Update from latest, immutably

Use functional setState for derived updates, and never mutate state.

React docsreact.dev
If your project has React Compiler enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
React CompilerRead docs ↗

From vercel-react-best-practices: rerender-functional-setstate and js-tosorted-immutable.

A callback stuck on mount

Add three todos, then remove one on each side.

Reads from closureBad
addItems
removeItem

    setItems(items.filter(…))deps []
    Functional updateGood
    addItems
    removeItem

      setItems(curr => curr.filter(…))deps []
      const addItems = useCallback((newItems) =>
        setItems((curr) => [...curr, ...newItems]), [])
      
      const removeItem = useCallback((id) =>
        setItems((curr) => curr.filter((item) => item.id !== id)), [])

      Three updates in one click

      Click fast, or turn on the delay.

      setCount(count + 1)Bad
      0
      expected 0in step
        setCount(c => c + 1)Good
        0
        expected 0in step
          function onClick() {
            setCount((c) => c + 1)
            setCount((c) => c + 1)
            setCount((c) => c + 1)
          }
          
          setTimeout(() => setCount((c) => c + 1), 800)

          A child sorts parent's array

          Re-render the parent, or change the sort key.

          users.sort(cmp)Bad

          Parent join order

            Child by age

              users contents
              Parent list
              sorted === users

              useMemo(() => users.sort(cmp), [users, key])
              users.toSorted(cmp)Good

              Parent join order

                Child by age

                  users contents
                  Parent list
                  sorted === users

                  useMemo(() => users.toSorted(cmp), [users, key])
                  const sorted = useMemo(
                    () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
                    [users]
                  )

                  Why it matters, pros and cons

                  Why it matters

                  An updater gets the latest state. .sort() mutates the owner's array in place.

                  Pros

                  • Stable callbacks with [] deps
                  • Correct under batching, timeouts and async
                  • New references, so memo sees the change

                  Cons

                  • Values ignoring the old one: set directly
                  • Updaters must be pure; StrictMode runs them twice
                  • toSorted copies: O(n) memory per call