Simplifying State Management: Implementing CRUD in React JS Using Zustand

Overview

In this Blog, we will discuss developing a simple CRUD (Create, Read, Update, Delete) application using React.js and Zustand as our state management solution.

Zustand is a small, fast, and scalable state management library for React that simplifies handling global states without the boilerplate found in other solutions like Redux.

What is Zustand ?

Zustand is a small, fast, scalable state management solution for React applications. Created by Jared Palmer and Daishi Kato. State management involves managing and updating the data that decides how an application behaves and looks.
Example: Consider an e-commerce website shopping card function. The products in the card, their amounts, and the total cost would all be managed by the state. The application modifies the state of the cart when a user adds an item, recalculation totals and updates associated components like the status of the checkout button or mini cart. Here adding an item into the cart and deleting the item from the cart can be easily achieved by using zustand.

Key Features of Zustand

  • Simplicity - Zustand has a minimal API, making it easy to learn and use.
  • No boilerplate - Unlike some other state management solutions Zustand requires very little setup code.
  • Hooks-based - Zustand leverages React Hooks, making it feel native to modern React development.
  • TypeScript Support - Zustand works great with TypeScript, providing through middleware.
  • Middleware Support - Zustand allows you to extend functionality through middleware.
  • Devtools Support - Zustand integrated well with Redux DevTools for debugging.
  • Framework agnostic - While primarily used with React, Zustand can be used in any Javascript Environment.
  • Single Store - Zustand encourages the use of a single store for all application states.
  • Subscriptions - Components subscribe to specific parts of the state, and re-render only when those parts change.

Step-by-Step Guide to Building CRUD with Zustand and React.js

  1. Setting Up the Project

a) Create a new React project - If you don’t have React set up, create a new app by typing in terminal

npx create-react-app zustand-crud

After successfully installed the react app redirect to zustand-crud directory

cd zustand-crud

After created react-app now install the zustand library.

b) Install Zustand - Zustand can be installed via npm or yarn :

npm install zustand
  1. Create a Store with Zustand

In Zustand, you can create a store (which is similar to a global state) with an easy-to-use API. We’ll use it to manage a list of users, and allow for CRUD operations (add, read, update, delete).

Create a store in the src/store.js file

import create from "zustand";

// Create the Zustand store to manage the user list
const useStore = create((set) => ({
  users: [],  // initial empty list of users
  addUser: (user) => set((state) => ({ users: [...state.users, user] })),
  deleteUser: (id) => set((state) => ({ users: state.users.filter(user => user.id !== id) })),
  updateUser: (updatedUser) =>
    set((state) => ({
      users: state.users.map((user) =>
        user.id === updatedUser.id ? updatedUser : user
      ),
    })),
}));

export default useStore;

Here we are created Zustand store and exported to use in React application

Explanation of Each Part in Zustand Store

create((set) => ({ ... }))
  • create is a function provided by Zustand that takes a callback function. This callback function receives set as an argument. 
  • set is used to modify the state within the store.
users: []
  • This defines the initial state of users, which is an empty array.
  •  It will hold the list of users in the app.
addUser: (user) => set((state) => ({ users: [...state.users, user] }))
  • This is an action method called addUser.
  •  It takes a user as an argument and adds it to the users array.
  • The set function is called to update the state. It uses the previous state (state.users) and adds the new user to the array using the spread operator [...].
  • state.users refers to the current list of users, and we add the new user to the list.
deleteUser: (id) => set((state) => ({ users: state.users.filter(user => user.id !== id) }))
  • This is the deleteUser action, which takes an id of the user to delete.
  • It calls set to update the state and filters out the user with the specified id from the users array using the filter method.
  • state.users.filter(user => user.id !== id) returns a new array with all users except the one with the matching id.
updateUser: (updatedUser) => set((state) => ({ users: state.users.map((user) => user.id === updatedUser.id ? updatedUser : user) }))
  • This is the updateUser action, which takes an updatedUser object.
  • It updates the user in the users array by checking if the id of each user matches the id of updatedUser. If it does, it replaces the old user with updatedUser; otherwise, it keeps the user unchanged.
  • state.users.map(...) creates a new array, where for each user, it checks if the id matches updatedUser.id. If so, it replaces the user with updatedUser; otherwise, it keeps the original user.
  1. Building the CRUD Interface in React - Let’s now create the main React component to interact with the store.
import React, { useState } from "react";
import useStore from "./store";

function App() {
  // Local state to handle form inputs
  const [userName, setUserName] = useState("");
  const [userAge, setUserAge] = useState("");
  const [editingUser, setEditingUser] = useState(null);
  const users = useStore((state) => state.users);
  const addUser = useStore((state) => state.addUser);
  const deleteUser = useStore((state) => state.deleteUser);
  const updateUser = useStore((state) => state.updateUser);
  
  // Handle form submission to add a new user or update an existing user
  const handleSubmit = (e) => {
    e.preventDefault();
    if (editingUser) {
      updateUser({ ...editingUser, name: userName, age: userAge });
    } else {
      addUser({ id: Date.now(), name: userName, age: userAge });
    }
    setUserName("");
    setUserAge("");
    setEditingUser(null);
  };
  
  // Handle edit user
  const handleEdit = (user) => {
    setUserName(user.name);
    setUserAge(user.age);
    setEditingUser(user);
  };
  
  return (
    <div className="App">
      <h1>CRUD with Zustand</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Name"
          value={userName}
          onChange={(e) => setUserName(e.target.value)}
        />
        <input
          type="number"
          placeholder="Age"
          value={userAge}
          onChange={(e) => setUserAge(e.target.value)}
        />
        <button type="submit">
          {editingUser ? "Update User" : "Add User"}
        </button>
      </form>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} ({user.age})
            <button onClick={() => handleEdit(user)}>Edit</button>
            <button onClick={() => deleteUser(user.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
export default App;

Create the App.js component

Here App.js is a Form UI where you can manage the create, Read, Update, and Delete actions performed by the user

Step 5: Run the Application

To run the application, use the following command:

npm start

Explanation

useStore is imported from the store.js file, where the Zustand store is defined. This allows us to access and manipulate the global state (the list of users). userName, userAge and other states managed by useState. Here you will access the global state actions from zustand store by importing the users, addUser, deleteUser, updateuser from zustand store.

  • handleEdit: This function is triggered when the "Edit" button next to a user's name is clicked. It sets the form's input fields (userName and userAge) to the values of the selected user's data. It also sets editingUser to the user being edited, so the form will know that it’s in "editing mode". Once after filling or editing the form we have to click the handleSubmit function
  • handleSubmit: This function is triggered when the form is submitted (either to add a new user or update an existing one).

Summary

In this blog, we’ve built a basic CRUD application using React.js and Zustand. With Zustand’s simple API, you can easily manage the ofstate in your React apps while keeping your codebase clean and scalable. Feel free to expand this application by adding more features, like a persistent state with local storage or even connecting to a backend API to fetch and store data remotely.