State Architecture — Series 2

Preview — 3 of 10 questions

getSnapshot returns a brand-new object literal ({ width, height }) on every single call, not just when the window actually resizes. What problem does this specifically cause with useSyncExternalStore?

javascript
function useWindowSize() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => ({ width: window.innerWidth, height: window.innerHeight }) // getSnapshot
  );
}
AThis is completely fine — useSyncExternalStore deep-compares snapshot contents automatically, so a new object with identical field values is correctly treated as unchanged
BuseSyncExternalStore requires getSnapshot to return a referentially stable value when nothing has actually changed — since this getSnapshot returns a new object on every call (including calls made just to check "did anything change," not just after a real resize event), React sees a "changed" snapshot on essentially every check, which can lead to excessive re-rendering or, in the worst case, React detecting this instability and logging an "getSnapshot should be cached" warning / infinite update loop protection
CThis only causes issues in Concurrent Mode — in legacy rendering mode this pattern is completely safe
DuseSyncExternalStore automatically wraps getSnapshot in its own internal useMemo, so this concern doesn't apply at the hook level, only to subscribe

Middleware in Redux forms a chain, each wrapping the next, with actions passing through in the order they're listed. Why would putting loggerMiddleware genuinely last (after any middleware that might transform or short-circuit an action, like a thunk-resolving middleware) typically be the more correct choice, versus listing it first?

javascript
const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(loggerMiddleware, analyticsMiddleware),
});
APlacing loggerMiddleware after action-transforming middleware means it logs the final, plain, fully-resolved action object right before it reaches the reducer — capturing what the reducer will actually see; placed first, it would instead log intermediate, possibly non-plain values (like a thunk function, before the thunk middleware has resolved it into real dispatched actions), which is much less useful for understanding what state changes actually occurred
BMiddleware order has no effect on behavior in Redux — .concat() always executes all listed middleware simultaneously in parallel, regardless of order
CloggerMiddleware must always run first because logging needs to happen before any state mutation occurs, and middleware order is unrelated to mutation timing
DThis is purely a stylistic convention with zero functional difference — Redux normalizes middleware execution order internally regardless of how .concat() is called

This pattern — a manually-built store with useSyncExternalStore-based selective subscriptions — is essentially how libraries like Zustand and Jotai avoid React Contexts fan-out re-render problem entirely. Whats the structural difference that lets it avoid fan-out, compared to plain useContext?

javascript
// A hand-rolled "selective subscription" Context alternative:
function createSelectableContext(initialState) {
  const listeners = new Set();
  let state = initialState;

  function setState(next) {
    state = next;
    listeners.forEach(l => l());
  }

  function useSelector(selector) {
    return useSyncExternalStore(
      (cb) => { listeners.add(cb); return () => listeners.delete(cb); },
      () => selector(state)
    );
  }

  return { useSelector, setState };
}
AuseSyncExternalStore is inherently faster than useContext at the same task — the fan-out problem is purely a performance issue that any faster primitive would fix
BThis pattern doesn't actually avoid fan-out — it has exactly the same all-consumers-re-render characteristic as useContext, just with different syntax
CuseSyncExternalStore-based subscriptions are managed entirely outside React's Context mechanism — instead of a single Provider value that all useContext consumers subscribe to as one unit, each useSelector(selector) call independently re-runs selector(state) on every store update and only actually triggers a re-render for that specific component if its own getSnapshot result changed — giving genuinely per-consumer, per-selector granularity that Context's single-value-reference model doesn't have
DThis pattern requires wrapping the entire app in a special <SelectiveProvider> component that Context-based approaches don't need

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.