prevent render by zustand
2023-11-03 03:55:25

Prevent render by zustand

code is based on commit

Context has unnecessary rerender issue

Initially I was using Context to share states and functions in different components.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// declare
export type Config = {
skipSpace: boolean;
showTone: boolean;
};

export type State = {
duration: number;
keystrokes: number;
accuracy: number;
inaccuracy: number;
};
const AppContext = createContext({
config: {} as Config,
setConfig: (_: Config) => {},
state: {} as State,
setState: (_: State) => {},
pause: () => {},
resume: () => {},,
} as ContextType);

// useContext in component
export default function Typing({ rawWords }: { rawWords: Word[] }) {
const {
config: { skipSpace, showTone },
setState,
pause,
resume,
} = useContext(AppContext);
...
}

The code works but has unnecessary rerenders.

For example, whenever state.keystrokes is changed by another component, the Typing is rerendered even though not using state.keystrokes.

This issue is caused by use of useContext, as long as I use useContext, every components will be rendered when one of the states is changed in this context by any other components.

use useShallow in Zustand to prevent unnecessary rerenders

Zustand offers a hook useShallow to make sure that component only renders when the picked states are shallowly changed.

Zustand use to object.is() compute shallowEqual.

1
2
3
4
5
6
7
8
// now, I use zustand and useShallow to ensure in only showTone or memoryMode change
// cause render
const { showTone, memoryMode } = useAppStore(
useShallow((state) => ({
showTone: state.showTone,
memoryMode: state.memoryMode,
}))
);

use effect and useShallow to update a non-render-ref

useShallow works pretty well on unnecessary rerenders, but soon I encountered a new problem.

In this situation, I’m using skipSpaceRef in a closure, and skipSpaceRef is not relative to UI render.

However if I use useShallow to get skipSpace, once the shallowEqual return false, this component will be rendered even though I’m now using skipSpace for UI render.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { showTone, memoryMode } = useAppStore(
useShallow((state) => ({
showTone: state.showTone,
memoryMode: state.memoryMode,
skipSpace: state.skipSpace
}))
);
const skipSpaceRef = useRef(skipSpace)
useEffect(()=>{
skipSpaceRef.current = skipSpace
},[skipSpace]
)

const handleKeyDown = (e: KeyboardEvent) => {
if (skipSpace){
// some actions but not about ui render
}
}

Zustand provides a way to track changes of states which are designed not to tigger render.

In this code, I use the subscribe API to subscribe a state change, and use useEffect to update its ref, it works perfectly to get a notify of a state change and not tigger a render in the component

Prev
2023-11-03 03:55:25