Updating Data with Xata

Great! Our to-do app now reads data from Xata. Reading is just a small part of what most apps do though, with CRUD (Create, Read, Update, and Delete) being far more common. Let's explore how we can CRUD-ify our Xata app in this guide.

If you're joining us in the middle, we covered querying Xata in the previous section. If you are not familiar with the topic, read that first before coming here for more context.

Securely Talking to Xata

We always recommend querying Xata from a secure environment, like a serverless function, to hide your API keys from users. It is unsafe to query Xata from a browser, because you risk leaking your API key. Going forward, we'll query Xata with Next.js API routes.

Toggling a To-do Item

Let's start with the basics: when we click a to-do item, we update the record on Xata to have is_done set to true. To do this, let's add an onClick event handler to the checkbox element:

// ./pages/index.tsx

import { FC } from "react";
import { XataClient } from "../util/xata";

type Props = Awaited<ReturnType<typeof getServerSideProps>>["props"];

const Index: FC<Props> = ({ todos }) => {
  return (
    <main>
      <h1>My To-do List</h1>
      <ul>
        <li>
          {todos.map((t) => (
            <label key={t.id}>
              <input
+                onClick={() => {
+                  fetch("/api/toggle-todo", {
+                    method: "POST",
+                    headers: {
+                      "Content-Type": "application/json",
+                    },
+                    body: JSON.stringify({ id: t.id, is_done: !t.is_done }),
+                  }).then(() => {
+                    window.location.reload();
+                  });
+                }}
                type="checkbox"
                checked={t.is_done}
              />
              {t.label}
            </label>
          ))}
        </li>
      </ul>
    </main>
  );
};

const xata = new XataClient();

export const getServerSideProps = async () => {
  const todos = await xata.db.items.getMany();
  return { props: { todos } };
};

export default Index;

Let's talk about what we just changed. We added this onClick prop to the checkbox:

  onClick={() => {
    fetch("/api/toggle-todo", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ id: t.id, is_done: !t.is_done }),
    }).then(() => {
      window.location.reload();
    });
  }}

What we're doing here is sending a request to our Next.js API route to toggle the to-do item. In the handler for this API route, we will talk to Xata and update the to-do item. Let's create a file called ./pages/api/toggle-todo.ts, and implement it like this:

// ./pages/api/toggle-todo.ts
import { NextApiHandler } from "next";
import { XataClient } from "../../util/xata";

const xata = new XataClient();

const handler: NextApiHandler = async (req, res) => {
  const { id, is_done } = req.body;
  await xata.db.items.update({ is_done, id });
  res.end();
};

export default handler;

Let's save and visit our app again.

Great! It works! We're successfully updating data on Xata. However, we can do better.

Let's Refactor!

Notice how we're instantiating a new XataClient() above? Let's factor that out into a utility function, and then import a single instance of the client in both places:

// ./util/xataClient.ts

import { XataClient } from "./xata";

export const xata = new XataClient();

And then in the files that use it,

// ./pages/index.tsx

import { FC } from "react";
-import { XataClient } from "../util/xata";
+import { xata } from "../util/xataClient";

type Props = Awaited<ReturnType<typeof getServerSideProps>>["props"];

const Index: FC<Props> = ({ todos }) => {
  return (
    <main>
      <h1>My To-do List</h1>
      <ul>
        <li>
          {todos.map((t) => (
            <label key={t.id}>
              <input
                onClick={() => {
                  fetch("/api/toggle-todo", {
                    method: "POST",
                    headers: {
                      "Content-Type": "application/json",
                    },
                    body: JSON.stringify({ id: t.id, is_done: !t.is_done }),
                  }).then(() => {
                    window.location.reload();
                  });
                }}
                type="checkbox"
                checked={t.is_done}
              />
              {t.label}
            </label>
          ))}
        </li>
      </ul>
    </main>
  );
};

-const xata = new XataClient();

export const getServerSideProps = async () => {
  const todos = await xata.db.items.getMany();
  return { props: { todos } };
};

export default Index;

for our index page, and this in our API handler:

// ./pages/api/toggle-todo.ts
import { NextApiHandler } from "next";
-import { XataClient } from "../../util/xata";
+import { xata } from "../../util/xataClient";

-const xata = new XataClient();

const handler: NextApiHandler = async (req, res) => {
  const { id, is_done } = req.body;
  await xata.db.items.update({ is_done, id });
  res.end();
};

export default handler;

Conclusion

Great! Now we have a more reusable instance of a XataClient that we can use in multiple places! Okay, we can now update data, but how can we delete it? Let's look at that next!


Last modified 1 mo17 days ago