restate / v1.0.0
Select only what you need
Choose the part of a store each component depends on, compare objects with shallow, group changes with batch, and connect outside events.
On this page
Pass a selectorTry itSelect several fields with shallowWrite your own comparisonGroup changes with batchSubscribe outside ReactConnect outside eventsPass a selector
Call the hook with a function that picks what the component needs. After every change restate runs the selector and compares the result with the previous one using Object.is. If they are equal, the component does not render.
The note changes. Does ItemCount render?
No. Its selector still returns the same number of items, so the selection is equal and React is not asked to render it.
const useCart = restate({ items: ['Book'], coupon: '', note: '' });
function ItemCount() {
// Re-renders only when the number of items changes.
const count = useCart((cart) => cart.items.length);
return <p>{count} items</p>;
}Try it
Change fields and watch the render counters. Turn shallow off to see a selector that returns a new object every time.
Preparing the example…
Select several fields with shallow
A selector that builds an object returns a new object every time, so Object.is always sees a change. Pass shallow as the second argument: it compares the object’s own fields one level deep.
shallow also compares arrays, Map and Set entries one level deep.
import { restate, shallow } from '@kkwaenggwari/restate';
function CartSummary() {
const { count, coupon } = useCart(
(cart) => ({ count: cart.items.length, coupon: cart.coupon }),
shallow,
);
return (
<p>
{count} items · {coupon || 'no coupon'}
</p>
);
}Write your own comparison
Any function (left, right) => boolean works as the second argument. Use it when shallow is not enough, such as comparing a list by ids.
const ids = useCart(
(cart) => cart.items.map((item) => item.id),
(left, right) => left.length === right.length && left.every((id, index) => id === right[index]),
);Group changes with batch
Every set notifies subscribers immediately. When one action changes several stores, wrap it in batch: memory updates immediately, and subscribers run once after the callback.
import { batch } from '@kkwaenggwari/restate';
function checkout() {
batch(() => {
useCart.set((cart) => ({ ...cart, items: [] }));
useOrder.set((order) => ({ ...order, status: 'paid' }));
});
// Subscribers run once, after both stores changed.
}Subscribe outside React
subscribeSelector calls you only when the selected value changes, with the same optional comparison function.
const stop = useCart.subscribeSelector(
(cart) => cart.items.length,
(count, previous) => analytics.track('cart-size', { count, previous }),
);Connect outside events
The second argument of restate can be an updater. It starts when the first component or subscriber starts listening and its cleanup runs when the last one stops, so browser events, sockets or sensors are connected only while needed.
Use start() and stop() to control it by hand.
import { restate } from '@kkwaenggwari/restate';
// Listens while at least one component or subscriber uses the store.
export const useOnline = restate(true, ({ setState }) => {
const update = () => setState(navigator.onLine);
window.addEventListener('online', update);
window.addEventListener('offline', update);
update();
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
};
});