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 pagePass a selectorTry itSelect several fields with shallowWrite your own comparisonGroup changes with batchSubscribe outside ReactConnect outside events

Pass 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.

JavaScript / JSX
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.

JavaScript / JSX
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.

JavaScript / JSX
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.

JavaScript / JSX
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.

JavaScript / JSX
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.

JavaScript / JSX
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);
  };
});

Search docs

Search titles, descriptions and text of every guide.