Effortlessly Manage State Updates in Zustand with Immer
Written on
Chapter 1: Understanding Zustand and Immer
In managing complex state, especially with deeply nested objects, Immer can significantly simplify the process. In this article, we'll illustrate how to incorporate it through a practical example, building on concepts from our previous discussion about selectors.
Installing Required Packages
To begin, you’ll need to install the necessary packages:
npm install immer
We will design a feature that allows users to select a character and lock it. Once locked, users cannot choose a different character until they unlock the current one. Make sure to wrap your store logic using Immer.
import { create } from "zustand";
import { createSelectors } from "@/store/createSelectors";
import { immer } from "zustand/middleware/immer";
// Your code here...
export type SelectedCharacters = Character & { // New property
locked: boolean;
};
type CharactersStore = {
// Your code here...
selectCharacter: (character: Character) => void;
character: SelectedCharacters; // New property
lockCharacter: () => void; // New method
unLockCharacter: () => void; // New method
};
export const useCharacters = createSelectors(create(
immer((set) => ({ // New function
// Your code...
character: undefined,
selectCharacter: (character: Character) => set(state => {
if (state.character?.locked) {
return state;}
return ({
...state,
character: {
...character,
locked: false,
},
});
}),
lockCharacter: () => set(state => ({
...state,
character: {
...state.character,
locked: true,
},
})),
unLockCharacter: () => set(state => ({
...state,
character: {
...state.character,
locked: false,
},
})),
}))
));
Creating a New Component
We will utilize store selectors to obtain character data and implement an event handler for locking and unlocking characters, similar to the logic seen in the Digimon and Pokemon components.
"use client";
import Image from "next/image";
import { useCharacters } from "@/store/use-characters";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
export function SelectedCharacter() {
const character = useCharacters.use.character();
const lockCharacter = useCharacters.use.lockCharacter();
const unLockCharacter = useCharacters.use.unLockCharacter();
function toggleCharacter() {
if (character.locked) {
unLockCharacter();} else {
lockCharacter();}
}
if (!character) {
return null;}
return (
<Card>
<CardHeader>
<CardTitle>Selected Character</CardTitle></CardHeader>
<CardContent>
<h2>{character.name}</h2>
<Button onClick={toggleCharacter}>
{character.locked ? "Unlock" : "Lock"}</Button>
</CardContent>
</Card>
);
}
Updating the Digimons Component
In this section, we’ll place the selection button within the card content. Ensure that you include the selectCharacter selector alongside existing selectors.
const selectCharacter = useCharacters.use.selectCharacter();
selectCharacter(digimon)}>Choose
Updating the Pokemons Component
We will apply the same methodology for the Pokemon component as we did for the Digimons.
const selectCharacter = useCharacters.use.selectCharacter();
selectCharacter(pokemon)}>Choose
Testing the Implementation
Let’s examine the current state of our selections.
Where Does Immer Fit In?
You're right to ask where Immer comes into play. We simply wrap our logic with Immer, keeping state updates straightforward. Immer enables us to modify specific properties directly without affecting the entire state object. Let’s update the store:
selectCharacter: (character: Character) => set(state => {
state.character = {
...character,
locked: true,
};
}),
lockCharacter: () => set(state => {
state.character.locked = true;
}),
unLockCharacter: () => set(state => {
state.character.locked = false;
}),
Final Testing
Everything should function as expected.
Conclusion
With Immer, you can avoid copying the entire state, allowing for targeted updates on specific properties, which is especially beneficial when managing complex structures. Remember, using Immer is optional; you can choose to implement it based on your needs.
Full Code Repository: [GitHub Link]
Thank you for engaging with this article. I hope you found it insightful! If you have any feedback or thoughts, feel free to reach out.
In Plain English 🚀
Thank you for being part of the In Plain English community! Before you leave, consider following us for more updates: X | LinkedIn | YouTube | Discord | Newsletter. Explore more content on Stackademic | CoFeed | Venture | Cubed, and find additional resources at PlainEnglish.io.
Chapter 2: Video Tutorials
In this chapter, we will provide video resources to enhance your understanding of Zustand and Immer.
This video demonstrates how to utilize Zustand with persistent storage for efficient state management in Next.js.
Quick guide on implementing Zustand State Management in just 5 minutes, tailored for Next.js users.