-
We recently upgraded to Zustand version 5 and we are noticing that some behavior that used to work now results in a The is shaped like this. (Irrelevant props excluded for simplicity.) import { create } from "zustand";
import type { Workflow } from "./workflow.ts";
import { toReactFlow } from "./utils";
interface WorkflowState {
workflow: Workflow;
getReactFlowGraph: () => { nodes: ReactFlowNode[]; edges: ReactFlowEdge[] };
}
export const createWorkflowStore = (workflow: Workflow) => {
return create<WorkflowState>((set, get) => ({
workflow,
getReactFlowGraph: () => {
const { workflow } = get();
return toReactFlow(workflow);
},
}));
}; In this store, However, when I try to call this function in a component, I end up with a 🚫 1. Calling Computed State Function in Selector export function WorkflowEditorInner() {
const { nodes, edges } = useWorkflowStore((s) => s.getReactFlowGraph());
return (
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} fitView>
<Background />
</ReactFlow>
);
} This fails with an maximum update depth error. 🚫 2. Calling Computed State Function in Selector with export function WorkflowEditorInner() {
const { nodes, edges } = useWorkflowStore(
useShallow((s) => s.getReactFlowGraph()),
);
return (
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} fitView>
<Background />
</ReactFlow>
);
} Also fails with maximum update depth error. 🚫 3. Computing the State Inside the Selector import { toReactFlow } from "./utils";
export function WorkflowEditorInner() {
const { nodes, edges } = useWorkflowStore((s) =>
toReactFlow(s.workflow),
);
return (
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} fitView>
<Background />
</ReactFlow>
);
} Sadly this also fails. 🚫 4. Computing the State Inside the Selector with import { toReactFlow } from "./utils";
export function WorkflowEditorInner() {
const { nodes, edges } = useWorkflowStore(
useShallow((s) => toReactFlow(s.workflow)),
);
return (
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} fitView>
<Background />
</ReactFlow>
);
} Nope. Sadly that doesn't work either. ✅ 5. Computing the State Inside the Selector with export function WorkflowEditorInner() {
const workflow = useWorkflowStore((s) => s.workflow);
const { nodes, edges } = toReactFlow(workflow);
return (
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} fitView>
<Background />
</ReactFlow>
);
} At last, no error! So my question is - what is the best practice for computed state in Zustand version 5? Option 5 feels a bit verbose and not very reusable. In Zustand 4, option 1 seemed to work just fine. Let me know what the best practice is here 👍 |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
It's a misunderstanding. Zustand 4 doesn't optimize rerenders in that case. Zustand 5 reveals the issue that was hidden in Zustand 4.
The best practice with vanilla Zustand regardless of its version is a custom hook, which technically means option 5.
There are some middlewares in the ecosystem such as https://github.com/cmlarsen/zustand-middleware-computed-state, but I'm not sure how much they are used. |
Beta Was this translation helpful? Give feedback.
It's a misunderstanding. Zustand 4 doesn't optimize rerenders in that case. Zustand 5 reveals the issue that was hidden in Zustand 4.
The best practice with vanilla Zustand regardless of its version is a custom hook, which technically means option 5.
There are some middlewares in the ecosystem such as https://github.com/cmlarsen/zustand-middleware-computed-state, but I'm not sure how much they are used.