Integrating API with State Management in React using Zustand
Published: 2024-05-31 16:01:00

# 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.

```typescript
// 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
```

## 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.

```typescript
// 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'
	});
}
```

## 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.

```typescript
// 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)
    	}));
	}
}));
```

## 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.

```typescript
// index.ts

export * from './store';
export * from './types';
export * from './api';
```

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:
```typescript
import { useProductStore } from './store';
import { Product } from './types';
import { fetchProducts } from './api';
```

We can now do this:
```typescript
import { useProductStore, Product, fetchProducts } from './index';
```

## 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.

```typescript
// 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;
```

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**