Re-render for what you read

A component shouldn't re-render for a value it never reads.

Paco Courseypaco.me
if you never use a certain value, why re-render when that value changes?
React Hook Getter Pattern · Sep 2021Read article ↗
Paco Courseypaco.me
When proxy get is invoked: track which object key is used, return the value
React Hook Getter Pattern · Sep 2021Read article ↗

Plain JS here, no React: the same idea as the hook getter.

1 · Change one value

Press a button. Count which widgets flash.

Notify everyoneBad
    for (const s of subs) s.run()Renders0
    Notify readers onlyGood
      if (s.keys.has(key)) s.run()Renders0
      function createStore(initial) {
        const state = { ...initial };
        const subs = new Set();
        const set = (key, value) => {
          if (state[key] === value) return;
          state[key] = value;
          for (const sub of subs) if (sub.keys.has(key)) sub.run();
        };
        const subscribe = (render) => {
          const sub = { keys: new Set(), run: () => {} };
          const proxy = new Proxy(state, {
            get(target, key) { sub.keys.add(key); return target[key]; },
          });
          sub.run = () => { sub.keys.clear(); render(proxy); };
          subs.add(sub);
          sub.run();
          return () => subs.delete(sub);
        };
        return { set, subscribe };
      }

      Why it matters, pros and cons

      Why it matters

      Every extra render is wasted work, and the reads already say which values matter.

      Pros

      • Subscriptions come from the render itself, so no selector list to keep in sync.
      • Unrelated updates cost zero renders, however many components subscribe.
      • Conditional reads work: keys are cleared and tracked again on every render.

      Cons

      • Proxy traps add overhead and hide the dependency list from the reader.
      • Reads outside render, like in effects or event handlers, are not tracked.
      • Spreading the whole state up front reads every key and defeats the point.