All resources
ReactIntermediate2 min read

React Performance Essentials

Derive instead of syncing, subscribe narrowly to stores, memoize on purpose and load heavy code on demand.

Derive values instead of syncing state

Copying props or other state into useState and keeping it in sync with useEffect causes an extra render and stale values. If something can be computed from what you already have, compute it during render.

TSX
// ✗ extra render, can drift out of syncconst [visible, setVisible] = useState(items);useEffect(() => {  setVisible(items.filter((item) => item.category === filter));}, [items, filter]);// ✓ derived during renderconst visible = items.filter((item) => item.category === filter);

Subscribe to the smallest slice of a store

Reading a whole Zustand store re-renders the component on every change — including keystrokes. Select only what you render; use useShallow when you need several fields at once.

status-bar.tsxTSX
import { useShallow } from "zustand/react/shallow";// ✗ re-renders whenever anything changesconst { language, theme } = useCodeEditorStore();// ✓ re-renders only when these values changeconst language = useCodeEditorStore((state) => state.language);const { fontSize, wordWrap } = useCodeEditorStore(  useShallow((state) => ({    fontSize: state.fontSize,    wordWrap: state.wordWrap,  })));

Memoize on purpose

  • useMemo for genuinely expensive work (parsing, tokenizing, large filters) — not for every value.
  • memo() for components that receive stable props but sit under a frequently re-rendering parent.
  • useCallback only when the function is a dependency of an effect or a prop of a memoized child.
  • Measure with the React DevTools Profiler before and after.

Load heavy code on demand

Editors, charts and 3D scenes can weigh hundreds of kilobytes. next/dynamic splits them into their own chunk and renders a placeholder meanwhile.

TSX
import dynamic from "next/dynamic";const CodeEditor = dynamic(  () => import("@/features/code-editor/components/editor-surface").then(    (mod) => mod.EditorSurface  ),  { ssr: false, loading: () => <EditorSkeleton /> });

Lists and keys

Use stable, unique ids as keys. Array indexes are only safe for lists that never reorder, filter or insert; otherwise React reuses the wrong DOM nodes and state.

For thousands of rows, paginate or virtualize (for example with @tanstack/react-virtual) so the DOM only contains what is on screen.