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.
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" })); // ✅ RerenderThe 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.
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/.
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) |