React Context is Officially Dead; Enter Zustand State Management.
In the ever-evolving landscape of frontend development, React Context has long been a stalwart for state management.
However, the winds of change are blowing as a new contender steps onto the scene: Zustand. In this article, we delve into Zustand by creating a basic interactive todo application.
We’ll take a close look at how Zustand reimagines state management, its core principles, and how it addresses some of the pain points that developers have faced with React Context.
Ready? Lets get started!
Table of Contents
- Setting Up A React/Next.js Project
- Dependencies Installation
- Creating Our Global Zustand Store
- Using Our Zustand Store
Setting Up A React/Next.js Project
- Before you begin, you will need to setup a Next.js 13 or basic React app. Both will work just fine as Next.js supports React client components.
Dependencies Installation
- The dependencies required for this project is minute, in fact you will only need to install two packages; the
zustand
package, anduuid
.
# we will need 'uuid' later to assign unique id's to our
# todos
npm i zustand uuid
pnpm add zustand uuid
yarn add zustand uuid
Creating Our Global Zustand Store
Now that the groundwork is complete, lets get coding.
- Begin by creating a file called
useTodosStore.ts
anywhere in your project workspace. - Copy the following code; it’s quite a mouthful, but we’ll break it down shortly.
import { v4 as uuid } from "uuid"
import { create } from "zustand"
type Todo = {
id: string
checked: boolean
}
export type Todos = Todo[] | null
interface TodosState {
todos: Todos
setTodos: (todos: Todos) => void
addTodo: () => void
removeTodo: (id: string) => void
updateTodoChecked: (id: string) => void
}
export const useTodosStore = create<TodosState>((set) => ({
todos: null,
// Simply takes in a todos list as a parameter and updates the todos
// state with that value.
setTodos: (todos) => set(() => ({ todos })),
// a new array 'updatedTodos' is created by removing a todo with a matching
// 'id' property.
removeTodo: (id) =>
set(({ todos }) => {
const updatedTodos = todos!.filter((todo) => todo.id !== id)
return { todos: updatedTodos }
}),
// Creates a new Todo object and appends it to the current todos state.
addTodo: () =>
set(({ todos }) => {
const newTodo: Todo = { id: uuid(), checked: false }
if (todos?.length) {
return { todos: [...todos, newTodo] }
} else {
return { todos: [newTodo] }
}
}),
// Searches for the todo in the todos array by id, then
// negates the current checked value and updates the state.
updateTodoChecked: (id) =>
set(({ todos }) => {
const updatedTodos = todos!.map((todo) => {
if (todo.id === id) {
return {
...todo,
checked: !todo.checked,
}
}
return todo
})
return { todos: updatedTodos }
}),
}))
- Firstly we import
create
fromzustand
, which is a function that allows us to create our global state, as well asuuid
to generate unique id’s for each todo. - We define a custom type
Todo
which has a uniqueid
as well as achecked
property. - We define another custom type
Todos
that represents our array ofTodo
objects or it could also be null, representing no todos. - Next we define the global store
useTodosStore
of generic typeTodosState
which containing the blueprint of ourtodos
state and our functions toadd
,remove
,set
, andupdate
todos. - Inside our store, we define our initial
todos
state, which is null in this case, representing no todos. - We then define the four state functions as shown; look at the comments for further explanations.
Using Our Zustand Store
I hope your still with me at this point. I chose this example because I feel like it’s made for zustand like bread is made for butter. This example therefore gives me the opportunity to fully showcase the power of zustand
state management.
- Open a file in React/Next.js where you can render a component, in my case I create a
page.tsx
file which is treated as a web page in Next.js 13. - Copy the following code:
// Only include this in Next.js
"use client"
import { useEffect } from "react"
import { useTodosStore } from "path-to-your-useTodosStore-file"
const Page = () => {
// Defines the global zustand state along with all it's functions
const { todos, setTodos, removeTodo, addTodo, updateTodoChecked } = useTodosStore()
// Begin by showing 1 todo on initial page render which is unchecked.
useEffect(() => {
setTodos([{ id: "1", checked: false }])
}, [])
return (
<div>
{todos?.map((todo) => (
<div className="flex">
<input
type="checkbox"
checked={todo.checked}
onChange={() => updateTodoChecked(todo.id)}
/>
<button onClick={() => removeTodo(todo.id)}>
Remove this todo -
</button>
</div>
))}
<button onClick={() => addTodo()}>Add a new todo +</button>
</div>
)
}
export default Page
- Simple right? We are importing the global state within our file, we then initialize the
todos
state with one todo, followed by rendering the todos, which are now fully interactive and stateful. Test it for yourself!
Here is an example of how it may look with proper styling applied:
- In the above example I made use of a ‘text input’ as well as the features we covered in this article.
- CHALLENGE: update the
useTodosStore.ts
file using what you’ve learnt to create a new state functionupdateTodoText
that manages the state of a ‘text input’ field for each todo. Then update your page to render each input field.
Congratulations! You are now a certified zustand
pro!
I hope I've convinced you of the many benefits of zustand
using the common todos
example, and hopefully I’ve convinced you to put React Context in the past.
If you enjoyed this article, check out my profile for many more stories like this, and stay tuned for future articles! 👍
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 💰 Free coding interview course ⇒ View Course
- 🧠 AI Tools ⇒ View Now
🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job