/react-crooks

Primary LanguageTypeScript

🦹 react-crooks

React Crooks is the counterpart to React hooks (by hook or by crook). It’ll steal your heart with how fast and easy it is to use.

Usage

useDiffState

useDiffState has the exact same API as React.useState:

import { useDiffState } from "react-crooks";

function Counter() {
  const [count, setCount] = useDiffState(0);

  return (
    <>
      <button type="button" onClick={() => setCount((v) => v - 1)}>
        -
      </button>
      The current count is: {count}
      <button type="button" onClick={() => setCount((v) => v + 1)}>
        +
      </button>
    </>
  );
}

But under-the-hood, we have a more intelligent layer than React.useState that won’t rerender if the values are the same:

import { useDiffState } from "react-crooks";

const [data, setData] = useDiffState({ foo: "bar" });

setData({ foo: "bar" }); // 🚫 no rerender
setData({ ...data, foo: "bar" }); // 🚫 no rerender
setData(() => ({ foo: "bar" })); // 🚫 no rerender

setData({ foo: "baz" }); // ✅ Rerender
setData(() => ({ foo: "bat" })); // ✅ Rerender

Comparisons

React.useState

The React reconciler already diffs and compares across re-renders, and tries to diff DOM trees. But it offers no tools for diffing objects and arrays, the bread-and-butter of state structures.

While not all rerenders are bad, and this is “just how React works,” if you find yourself ever writing code like the following:

setState((oldValue) =>
  oldValue.foo !== newValue ? { ...oldValue, [foo]: newValue } : oldValue
);

Then you can swap it out for react-crooks and save boilerplate and boost performance by letting the diffing engine handle that.

Performance-wise, for most small-ish objects and arrays, the performance is equal to React.useState(). But for larger objects and arrays, the cost of diffing starts to slowly slip behind React.useState(). For your largest, deepest-nested data structures, compare the cost of diffing vs the rerender(s) you’re saving. Every situation will be different, and the API is meant to be interchangeable so you can opt back into React.useState() in spots that benefit.

Benchmarks

You can run benchmark tests yourself with pnpm run browser. These are benchmarks from a test that updates state and rerenders a simple component. These results were tested on a 2024 Macbook Air M2. Actual code tested lives in src/__tests__/bench/.

useDiffState

Tip

Remember that this is only the time it takes to rerender. This does not include downstream and child rerenders which is what the library exists to prevent.

Test react-crooks React.useState Immer Mutative
Setup 94k 94k 94k 92k (3% slower)
Same String 93k 93k 92k (1% slower) 88k (6% slower)
New String 93k 93k 93k 90k (3% slower)
Object (new instance) 93k 93k 90k (3% slower) 43k (54% slower)
Object (spread) 83k (10% slower) 92k 77k (17% slower) 41k (56% slower)
Object (value change) 91k (2% slower) 93k 91k (2% slower) 44k (53% slower)
Large object (value change) 69k (4% slower) 72k 71k (1% slower) 36k (50% slower)
Large object (same value) 65k (12% slower) 74k 73k (1% slower) 39k (48% slower)
Array (same value) 84k 84k 76k (10% slower) 37k (56% slower)
Array (value change) 58k (38% slower) 92k 60k (35% slower) 60k (35% slower)