I’m trying to create a post component where each post has its own state. The post component is really complex, so it’s necessary to use the React context hook.
import { createContext, useContext, PropsWithChildren } from "react";
import { create, StoreApi, UseBoundStore } from "zustand";
// Define the type of the context
type LikesStore = {
count: number;
isLiked: boolean;
initialize: (initialCount: number) => void;
toggleLiked: () => void;
};
// Define the type of the context
type LikesContextType = UseBoundStore<StoreApi<LikesStore>>;
// Create the context
const likesContext = createContext<LikesContextType | undefined>(undefined);
// Create a hook to use the context
const useLikesContext = () => {
const context = useContext(likesContext);
if (context === undefined) {
throw new Error("useLikesContext must be used within a LikesProvider");
}
return context;
};
// Create a provider
const LikesProvider = ({ children }: PropsWithChildren) => {
// Zustand store
const useLikesStore = create<LikesStore>((set) => ({
count: 5,
isLiked: false,
initialize: (initialCount) => set({ count: initialCount }),
toggleLiked: () =>
set((state) => ({
isLiked: !state.isLiked,
count: state.isLiked ? state.count - 1 : state.count + 1,
})),
}));
return (
<likesContext.Provider value={useLikesStore}>
{children}
</likesContext.Provider>
);
};
export { LikesProvider, useLikesContext };
Here’s how it’s used in different components within the Post component:
function Counter() {
const { count } = useLikesContext()();
return (
<div className="my-3 flex h-3 items-center gap-2 px-2">
<span className="text-xs text-zinc-300">{count} likes</span>
</div>
);
}
function LikeButton() {
const { toggleLiked, isLiked } = useLikesContext()();
return (
<button
className="group flex aspect-square select-none items-center justify-center gap-2 rounded-full bg-zinc-700 px-3 py-2 text-sm font-medium text-zinc-100"
onClick={toggleLiked}
>
<span
className={twJoin(
"material-symbols-rounded scale-100 text-xl transition-all duration-150 ease-in-out group-active:scale-90",
isLiked ? "filled text-pink-600" : "text-zinc-100"
)}
>
favorite
</span>
</button>
);
}
Is this approach appropriate, or should I revert to using useState
or useReducer
?
Because I’m leaning toward the Zustand approach because it suits my preferences better.
2
Answers
Zustand has its own Provider functionality.
zustand/context
https://docs.pmnd.rs/zustand/previous-versions/zustand-v3-create-context
From the example:
If it does not fit you, you can use your implementation as well. With few changes.
If you create a store inside a component, you have to memoize it, in order to create it only once and not on each render.
Better change your custom hook into this:
It is a hook, that returns another hook. This ensures that the returned hook will be called only at top level.
I don’t see selectors in your example. If you don’t use them, no need to use zustand, context will do the same thing.
You can, but your
LikesProvider
will create a new store each time you render it. You should also consider using theuseStore
hook inuseLikesContext
, rather than just extracting the whole store.N.b. ‘zustand/context` mentioned in @Oktay’s answer is deprecated, and will be removed in v5
Adapting the documentation example:
Which you would then use in components like