963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
2.2 KiB
2.2 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| useState Dispatch updaters for State That Depends on Current Value | MEDIUM | avoids stale closures, prevents unnecessary re-renders | state, hooks, useState, callbacks |
Use Dispatch Updaters for State That Depends on Current Value
When the next state depends on the current state, use a dispatch updater
(setState(prev => ...)) instead of reading the state variable directly in a
callback. This avoids stale closures and ensures you're comparing against the
latest value.
Incorrect (reads state directly):
const [size, setSize] = useState<Size | undefined>(undefined);
const onLayout = (e: LayoutChangeEvent) => {
const { width, height } = e.nativeEvent.layout;
// size may be stale in this closure
if (size?.width !== width || size?.height !== height) {
setSize({ width, height });
}
};
Correct (dispatch updater):
const [size, setSize] = useState<Size | undefined>(undefined);
const onLayout = (e: LayoutChangeEvent) => {
const { width, height } = e.nativeEvent.layout;
setSize((prev) => {
if (prev?.width === width && prev?.height === height) return prev;
return { width, height };
});
};
Returning the previous value from the updater skips the re-render.
For primitive states, you don't need to compare values before firing a re-render.
Incorrect (unnecessary comparison for primitive state):
const [size, setSize] = useState<Size | undefined>(undefined);
const onLayout = (e: LayoutChangeEvent) => {
const { width, height } = e.nativeEvent.layout;
setSize((prev) => (prev === width ? prev : width));
};
Correct (sets primitive state directly):
const [size, setSize] = useState<Size | undefined>(undefined);
const onLayout = (e: LayoutChangeEvent) => {
const { width, height } = e.nativeEvent.layout;
setSize(width);
};
However, if the next state depends on the current state, you should still use a dispatch updater.
Incorrect (reads state directly from the callback):
const [count, setCount] = useState(0);
const onTap = () => {
setCount(count + 1);
};
Correct (dispatch updater):
const [count, setCount] = useState(0);
const onTap = () => {
setCount((prev) => prev + 1);
};