DEV Community

Gerald Hamilton Wicks
Gerald Hamilton Wicks

Posted on

Integrating API with State Management in React using Zustand

Integrating API with State Management in React using Zustand

State management is a crucial aspect of building scalable and maintainable React applications. Zustand is a lightweight and flexible state management library that provides a simple way to manage your application's state. In this article, we will demonstrate how to integrate an API with Zustand to manage product data.

Type Definitions

First, let's define the types we will be using. We have a Product type that represents a product object and a ProductStore type that describes the structure of our Zustand store.

// types.ts

export type Product = {
    id: number,
    title: string
};

export type ProductStore = {
    products: Array<Product>,
    loadProducts: () => Promise<void>,
    createOneProduct: (value: Product) => Promise<void>,
    updateOneProduct: (value: Product) => Promise<void>,
    deleteOneProduct: (id: number) => Promise<void>
}

export type StoreSet =
(partial:
    ProductStore |
    Partial<ProductStore> |
    ((state: ProductStore) => ProductStore |
    Partial<ProductStore>),
replace?:
boolean | undefined) => void
Enter fullscreen mode Exit fullscreen mode

API Functions

Next, we define the API functions that will handle fetching, creating, updating, and deleting products. These functions will interact with our backend server.

// api.ts

import { Product } from "./types";

const BASE_URL = 'http://localhost:3002/product';

export async function fetchProducts(): Promise<Array<Product>> {
    const response = await fetch(BASE_URL);
    const data = await response.json();
    return data.products;
}

export async function createProduct(newProduct: Product): Promise<Product> {
    const response = await fetch(BASE_URL, {
        method: 'POST',
        body: JSON.stringify(newProduct)
    });
    const data = await response.json();
    return data.product;
}

export async function updateProduct(updatedProduct: Product): Promise<Product> {
    const response = await fetch(BASE_URL, {
        method: 'PUT',
        body: JSON.stringify(updatedProduct)
    });
    const data = await response.json();
    return data.product;
}

export async function deleteProduct(id: number): Promise<void> {
    await fetch(`${BASE_URL}/${id}`, {
        method: 'DELETE'
    });
}
Enter fullscreen mode Exit fullscreen mode

Zustand Store

Now, let's create our Zustand store. This store will use the API functions we defined to manage the product data. We will define methods for loading, creating, updating, and deleting products.

// store.ts

import { create } from "zustand";
import { Product, ProductStore } from "./types";
import { createProduct, deleteProduct, fetchProducts, updateProduct } from "./api";

export const useProductStore = create<ProductStore>()((set) => ({
    products: [],
    loadProducts: async () => {
        const products = await fetchProducts();
        return set(state => ({ ...state, products }));
    },
    createOneProduct: async (newProduct: Product) => {
        const product = await createProduct(newProduct);
        return set(state => ({
            ...state,
            products: [...state.products, product]
        }));
    },
    updateOneProduct: async (updatedProduct: Product) => {
        const product = await updateProduct(updatedProduct);
        return set(state => ({
            ...state,
            products: state.products.map(p => p.id === product.id ? product : p)
        }));
    },
    deleteOneProduct: async (id: number) => {
        await deleteProduct(id);
        return set(state => ({
            ...state,
            products: state.products.filter(product => id !== product.id)
        }));
    }
}));
Enter fullscreen mode Exit fullscreen mode

Centralized Exports with index.ts

To streamline our imports and make our codebase more organized, we can create an index.ts file. This file will re-export everything from our store, types, and api files, allowing us to import these modules from a single location.

// index.ts

export * from './store';
export * from './types';
export * from './api';
Enter fullscreen mode Exit fullscreen mode

With this setup, we can import the store, types, and API functions in other parts of our application using a single import statement.

For example, instead of doing this:

import { useProductStore } from './store';
import { Product } from './types';
import { fetchProducts } from './api';
Enter fullscreen mode Exit fullscreen mode

We can now do this:

import { useProductStore, Product, fetchProducts } from './index';
Enter fullscreen mode Exit fullscreen mode

Using the Store in a React Component

To utilize the store in a React component, we can use the useProductStore hook to access the state and actions defined in the store. Here's an example of how to use it in an App component.

// App.tsx

import { useProductStore } from './index';

function App() {  
  const {
    products,
    loadProducts,
    createOneProduct,
    updateOneProduct,
    deleteOneProduct
  } = useProductStore();

  return (
    <div className="App">
    <h1>Product Management</h1>
    <button onClick={loadProducts}>Load Products</button>
    <ul>
        {products.map(product => (
        <li key={product.id}>{product.title}</li>
        ))}
    </ul>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, we use the useProductStore hook to access the products and the actions to load, create, update, and delete products. We provide a button to load the products and display them in a list.

Conclusion

By following this structure, we have successfully integrated an API with Zustand for managing product data. This approach keeps our state management logic clean, modular, and easy to maintain. Zustand's simplicity and flexibility make it an excellent choice for managing state in React applications.

With the types, API functions, and store separated into their respective files, our codebase is well-organized and scalable, allowing us to easily extend and maintain the application as it grows.

Feel free to use this approach in your projects to streamline state management and API integration with Zustand. Happy coding!


Files:

  1. types.ts
  2. api.ts
  3. store.ts
  4. index.ts

Top comments (0)