Building a CRUD App with Next.js, React Query, React Hook Form, and Yup
Tags: webdev, nextjs, javascript, programming
Published: 2023-01-04 05:56:00

In this tutorial, you will learn how to build a CRUD (create, read, update, delete) app using Next.js, React Query, React Hook Form, and Yup.

You will learn how to use these tools to build a CRUD app that allows users to create, read, update, and delete items stored on a server. You will also learn how to use React Query to fetch and cache data, React Hook Form to manage forms, and Yup to validate form data.

## **Next.js**

Next.js is a framework for building server-rendered React applications. It provides features such as automatic code splitting, optimized performance, and simple client-side routing.
For more information visit [NextJs](https://nextjs.org/)

## **ReactQuery**

React Query is a library for fetching, caching, and updating asynchronous data in React apps. It is designed to be easy to use, lightweight, and intuitive, and it can help improve the performance and user experience of your app by reducing the number of network requests and optimizing the caching of data.
For more information visit [React Query](https://react-query-v3.tanstack.com/)

## **React Hook Form**

React Hook Form is a library for managing forms in React apps. It provides a simple and flexible API for handling form validation, submission, and other common form tasks, and it is designed to be easy to use with React functional components and the React Hooks API.
For more information visit https://react-hook-form.com/

## **Yup**

Yup is a JavaScript object schema validator and object parser. It provides an easy-to-use, expressive, and powerful way to validate and transform data.

You can use Yup with React Hook Form to provide more advanced validation for your forms. To use Yup with React Hook Form, you can pass a Yup validation schema to the validationSchema option of the useForm hook.
For more information visit [Yup Validator](https://github.com/jquense/yup)

---

## **Tutorial**

First, let's set up our component for creating a new item. We'll use React Hook Form and Yup to handle form validation and submission:

```javascript 
import { useForm } from 'react-hook-form'
import * as Yup from 'yup'

const validationSchema = Yup.object().shape({
  name: Yup.string().required('Name is required'),
})

/* Create Item Form */
function CreateItemForm() {

  /* Hooks */
  const { handleSubmit, register, errors } = useForm({
    validationSchema,
  })

  /* HandleOnSubmit */
  const onSubmit = async (data) => {
    try {
      const res = await fetch('/api/items', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      })
      const newItem = await res.json()
      console.log(newItem) 
      // log the newly created item to the console
    } catch (error) {
      console.error(error)
    }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="name">Name:</label>
      <input
        name="name"
        ref={register}
      />
      {errors.name && <span>{errors.name.message}</span>}
      <br />
      <button type="submit">Create Item</button>
    </form>
  )
}
```

In the above **CreateItemForm** component, we define a Yup validation schema that requires the name field to be filled out. We pass this validation schema to the useForm hook, and then use the errors object that is returned by the hook to display error messages to the user if the form is invalid.

---

Next, let's set up a component for reading a list of items. We'll use React Query to fetch the list of items from the server and cache the result:

```javascript
import { useQuery } from 'react-query'

/* Fetch List of Items */
function ItemList() {
  const { data, error } = useQuery('items', () => 
    fetch('/api/items').then((res) => res.json())
  )

  if (error) return <div>Error: {error.message}</div>
  if (!data) return <div>Loading...</div>

  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}
```

---

Now let's set up a component for **updating an item**. We'll again use **React Hook Form** and **Yup** to handle form validation and submission, and **React Query** to fetch the item data and update the cache when the item is successfully updated:

```javascript
import { useForm } from 'react-hook-form'
import { useQuery, useMutation } from 'react-query'
import * as Yup from 'yup'

const validationSchema = Yup.object().shape({
  name: Yup.string().required('Name is required'),
})

/* Update Item Form */
function UpdateItemForm({ itemId }) {
  
  const { data: item, isLoading, error } = useQuery(
    ['item', itemId],
    () => fetch(`/api/items/${itemId}`).then((res) => res.json())
  )

  const [updateItem, { isUpdating }] = useMutation(async (data) => {
    const res = await fetch(`/api/items/${itemId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    })
    return res.json()
  })

  const { handleSubmit, register, errors } = useForm({
    defaultValues: { name: item.name },
    validationSchema,
  })

  const onSubmit = async (data) => {
    try {
      const updatedItem = await updateItem(data)
      console.log(updatedItem) // log the updated item to the console
    } catch (error) {
      console.error(error)
    }
  }

  if (error) return <div>Error: {error.message}</div>
  if (isLoading) return <div>Loading...</div>

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="name">Name:</label>
      <input
        name="name"
        ref={register}
      />
      {errors.name && <span>{errors.name.message}</span>}
      <br />
      <button type="submit" disabled={isUpdating}>
        Update Item
      </button>
    </form>
  )
}
```

In the **UpdateItemForm** component, we define a **Yup validation schema **that requires the name field to be filled out. We pass this validation schema to the useForm hook, and then use the errors object that is returned by the hook to display error messages to the user if the form is invalid.

---

Finally, let's set up a component for **deleting an item.** We'll use React Query's **useMutation** hook to send a **DELETE** request to the server and update the cache when the item is successfully deleted:

```javascript
import { useMutation } from 'react-query'

function DeleteItemButton({ itemId }) {
  const [deleteItem, { isDeleting }] = useMutation(async () => {
    const res = await fetch(`/api/items/${itemId}`, {
      method: 'DELETE',
    })
    return res.json()
  }, {
    onSuccess: () => {
      console.log('Item deleted!') 
      // log a success message to the console
    },
  })

  return (
    <button onClick={deleteItem} disabled={isDeleting}>
      Delete Item
    </button>
  )
}
```

---

## **useMutation (React Query)**

`useMutation` is a hook provided by the react-query library that allows you to execute mutations (such as creating, updating, or deleting data) in your application. It returns an array with two elements: the mutation function, and an object containing the status of the mutation (e.g. loading, error, or success).

A simple example of how you might use useMutation:

```javascript
import { useMutation } from 'react-query';

const [createUser, { status: createStatus }] = useMutation(
  async (formData) => {
    const response = await fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(formData),
    });
    return response.json();
  },
  {
    onSuccess: () => {
      console.log('User created successfully');
      refetch();
    },
  }
);
```

In this example, the createUser function is used to execute the mutation by sending a POST request to the /api/users endpoint. The createStatus variable contains the status of the mutation (e.g. loading, error, or success). The onSuccess option is used to specify a callback function that will be executed when the mutation is successful.

## **useQuery (React Query)**

`useQuery` is another hook provided by the react-query library that allows you to fetch data in your application. It returns an object containing the data, a flag indicating whether the data is currently being fetched, an error object if an error occurred, and a function for refetching the data.

Example of how you might use useQuery:

```javascript
import { useQuery } from 'react-query';

const { data: users, isLoading, error, refetch } = useQuery('users', async () => {
  const response = await fetch('/api/users');
  return response.json();
});

if (isLoading) return 'Loading...';
if (error) return 'An error occurred';
```

Here, the **useQuery** hook is used to fetch a list of users from the /api/users endpoint. The data property of the returned object contains the fetched data, the isLoading flag is true while the data is being fetched, and the error object contains any error that occurred. The refetch function can be called to manually refetch the data.

---

## **Conclusion**

I hope that this tutorial has helped you understand how to build a CRUD app with Next.js, React Query, React Hook Form, and Yup. These tools are powerful and can help you create user-friendly, high-performance apps with minimal effort.

Remember to use Next.js to build server-rendered React apps, React Query to fetch and cache asynchronous data, React Hook Form to manage forms, and Yup to validate form data. With these tools in your toolbelt, you should have everything you need to build a CRUD app that meets the needs of your users.

If you have any questions or need further assistance, don't hesitate to ask!