Table of Contents
- Introduction to State Management
- Understanding Zustand
- Types of State
- Getting Started with Zustand
- Key Features of Zustand
- Implementing Zustand
- Zustand vs. Other State Management Solutions
- System Design of Zustand
- Persisting State with Zustand
- Using Zustand Outside React Components
- Real-World Examples
- Conclusion
- Tips: Handling Asynchronous Code with Zustand
1. Introduction to State Management
State management is a crucial aspect of modern web development, especially in complex applications. It involves handling the data that can change over time and ensuring that this data is consistently represented across the entire application. Effective state management leads to more predictable and maintainable applications.
2. Understanding Zustand
Zustand is a small, fast, and scalable state management solution for React applications. Created by Jared Palmer and Daishi Kato, Zustand offers a simple and intuitive API that makes state management less cumbersome compared to other solutions.
3. Types of State
Before diving deeper into Zustand, let's understand the different types of state in web applications:
- Local State: State that is specific to a component and doesn't need to be shared with other parts of the application.
- Global State: State that needs to be accessed and modified by multiple components across the application.
- Remote State: State that represents data fetched from an external source, typically an API.
Zustand excels in managing both local and global state, and can be integrated with solutions for remote state management.
4. Getting Started with Zustand
To start using Zustand, first install it via npm, yarn or pnpm:
npm install zustand
# or
yarn add zustand
# or
pnpm add zustand
5. Key Features of Zustand
Zustand comes with several features that make it stand out:
- 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 excellent type inference.
- Middleware support: Zustand allows you to extend its functionality through middleware.
- Devtools support: Zustand integrates well with Redux DevTools for debugging.
- Framework agnostic: While primarily used with React, Zustand can be used in any JavaScript environment.
6. Implementing Zustand
Let's look at a basic implementation of Zustand:
import { create } from 'zustand'
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
function BearCounter() {
const bears = useStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
In this example, we create a store with a bears
state and two actions to modify it. The BearCounter
and Controls
components can then access and modify the state using the useStore
hook.
7. Zustand vs. Other State Management Solutions
Let's compare Zustand with other popular state management solutions:
Zustand vs. Redux
Pros of Zustand:
- Simpler API with less boilerplate
- No need for action creators or switch statements
- Smaller bundle size
Cons:
- Less established ecosystem
- Fewer middleware options
Zustand vs. MobX
Pros of Zustand:
- More straightforward, less magical API
- Better performance for larger applications
Cons:
- Less reactive by default
- No computed values out of the box
Zustand vs. Recoil
Pros of Zustand:
- Simpler mental model
- Works outside of React
Cons:
- Less granular reactivity
- No built-in atom family concept
8. System Design of Zustand
Zustand's system design is based on a few key principles:
- Single store: Zustand encourages the use of a single store for all application state.
- Immutability: State updates are handled immutably, ensuring predictable behavior.
- Subscriptions: Components subscribe to specific parts of the state, re-rendering only when those parts change.
- Middleware: Zustand uses a middleware system for extending functionality.
This design allows Zustand to be both simple and powerful, providing excellent performance even in large applications.
9. Persisting State with Zustand
Zustand makes it easy to persist state, which is crucial for many applications. Here's an example using the persist
middleware:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useStore = create(persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 }),
}),
{
name: 'food-storage', // unique name
getStorage: () => localStorage, // (optional) by default, 'localStorage' is used
}
))
This will automatically save the state to localStorage and rehydrate it when the app reloads.
10. Using Zustand Outside React Components
One of Zustand's strengths is that it can be used outside of React components. This is particularly useful for integrating with other parts of your application or for testing:
const { getState, setState } = useStore
// Getting state
console.log(getState().bears)
// Setting state
setState({ bears: 10 })
// Using actions
getState().increasePopulation()
11. Real-World Examples
Let's look at some real-world examples of using Zustand:
Authentication State
import { create } from 'zustand'
const useAuthStore = create((set) => ({
user: null,
isAuthenticated: false,
login: (userData) => set({ user: userData, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}))
// Usage in a component
function LoginButton() {
const { isAuthenticated, login, logout } = useAuthStore()
const handleAuth = () => {
if (isAuthenticated) {
logout()
} else {
// Simulate login
login({ id: 1, name: 'John Doe' })
}
}
return (
<button onClick={handleAuth}>
{isAuthenticated ? 'Logout' : 'Login'}
</button>
)
}
Shopping Cart
import { create } from 'zustand'
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (itemId) => set((state) => ({
items: state.items.filter((item) => item.id !== itemId),
})),
clearCart: () => set({ items: [] }),
total: 0,
updateTotal: () => set((state) => ({
total: state.items.reduce((sum, item) => sum + item.price, 0),
})),
}))
// Usage in components
function CartSummary() {
const { items, total, removeItem } = useCartStore()
return (
<div>
{items.map((item) => (
<div key={item.id}>
{item.name} - ${item.price}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<div>Total: ${total}</div>
</div>
)
}
Theme Switcher
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useThemeStore = create(persist(
(set) => ({
theme: 'light',
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light',
})),
}),
{
name: 'theme-storage',
}
))
// Usage in a component
function ThemeToggle() {
const { theme, toggleTheme } = useThemeStore()
return (
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
)
}
12. Conclusion
Zustand offers a refreshing approach to state management in React applications. Its simplicity, flexibility, and performance make it an excellent choice for both small and large projects. By reducing boilerplate and providing a straightforward API, Zustand allows developers to focus on building features rather than managing complex state logic.
While it may not have the extensive ecosystem of some older state management solutions, Zustand's design principles and ease of use make it a compelling option for modern React development. Its ability to work outside of React components and easy integration with persistence solutions further extend its utility.
For many React applications, Zustand strikes an excellent balance between simplicity and power, making it worth considering for your next project.
Bonus tips:
Zustand also handles asynchronous functions/code really well and without the need for any Middleware setup.
Let's talk a bit about that:
13. Handling Asynchronous Code with Zustand
One of Zustand's strengths is its simplicity in handling asynchronous operations without the need for additional middleware or complex setups. This makes it particularly easy to work with API calls, data fetching, and other asynchronous tasks.
How Zustand Handles Asynchronous Code
Zustand's approach to asynchronous code is straightforward:
- Direct Integration: Asynchronous functions can be defined directly in the store.
- No Special Syntax: You don't need to use special action creators or thunks.
-
State Updates: You can update the state within async functions using the
set
function. - Error Handling: Error states can be managed directly within the async functions.
Implementing Asynchronous Code
Here's an example of how to implement asynchronous code in Zustand:
import { create } from 'zustand'
const useUserStore = create((set) => ({
user: null,
isLoading: false,
error: null,
fetchUser: async (userId) => {
set({ isLoading: true, error: null });
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
const userData = await response.json();
set({ user: userData, isLoading: false });
} catch (error) {
set({ error: error.message, isLoading: false });
}
},
}));
// Usage in a component
function UserProfile({ userId }) {
const { user, isLoading, error, fetchUser } = useUserStore();
React.useEffect(() => {
fetchUser(userId);
}, [userId]);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
In this example:
- We define an async
fetchUser
function directly in the store. - The function manages loading and error states alongside the user data.
- We use the
set
function to update the state at different points in the async operation. - In the component, we can use the store's state and actions as usual, with React hooks handling the component lifecycle.
Benefits of Zustand's Approach to Async Code
- Simplicity: No need for additional middleware or complex action creators.
- Flexibility: You can structure your async logic however you prefer.
- Readability: Async operations are defined close to the relevant state.
- Easy Testing: Async functions can be easily unit tested.
Comparison with Other Solutions
Unlike Redux, which often requires middleware like Redux Thunk or Redux Saga for handling async operations, Zustand's approach is much more straightforward. This simplicity can lead to less boilerplate and a gentler learning curve, especially for developers new to state management.
MobX and Recoil also offer ways to handle async operations, but Zustand's approach might be considered more intuitive due to its direct use of async/await syntax without additional abstractions.
In conclusion for async
Zustand's handling of asynchronous code exemplifies its philosophy of simplicity and flexibility. By allowing developers to write async functions directly in the store without special syntax or middleware, Zustand makes it easy to manage complex state operations while keeping the codebase clean and readable.
This approach to async code, combined with Zustand's other features like its small bundle size and easy setup, makes it an excellent choice for projects of all sizes, particularly those that involve significant asynchronous state management.
Hope this "kinda guide" was useful and insightful for any of you that's thinking on how to manage your global application state.
Thank you and Happy coding.
Have a look on my website at https://www.ricardogesteves.com
Follow me @ricardogesteves
X(twitter)
Top comments (4)
Awesome 👌
hi @rajgohel , thank you. I'm glad you like it.
Nice article!!!
Thank you a lot @dgesteves