DEV Community

Cover image for Building a CRUD App with Next.js, React Query, React Hook Form, and Yup
⚡ Nirazan Basnet ⚡ for IntegridSolutions

Posted on

Building a CRUD App with Next.js, React Query, React Hook Form, and Yup

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

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

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


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:

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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();
    },
  }
);
Enter fullscreen mode Exit fullscreen mode

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:

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';
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (4)

Collapse
 
leob profile image
leob

Fantastic!

Collapse
 
nirazanbasnet profile image
⚡ Nirazan Basnet ⚡

Thankyou :)

Collapse
 
monotype profile image
Yarik Gucci

Are you really created 2 separate component for edit and update? Why just not reuse one?

Collapse
 
nirazanbasnet profile image
⚡ Nirazan Basnet ⚡

Yes we can reuse one. For this article I just want to make how it works creating separate component just to make clear and thanks for the reply.