Architecture Patterns — Series 3

Preview — 3 of 10 questions

Exposing Select.Option as a static property of Select, with state shared internally via SelectContext, is the Compound Component pattern. What does this actually buy a consumer compared to a single monolithic <Select options={[...]} /> component?

javascript
function Select({ children }) {
  const [selected, setSelected] = useState(null);
  return (
    <SelectContext.Provider value={{ selected, setSelected }}>
      <div className="select">{children}</div>
    </SelectContext.Provider>
  );
}

Select.Option = function Option({ value, children }) {
  const { setSelected } = useContext(SelectContext);
  return <li onClick={() => setSelected(value)}>{children}</li>;
};

<Select>
  <Select.Option value="js">JavaScript</Select.Option>
  <Select.Option value="ts">TypeScript</Select.Option>
</Select>
AIt has no functional benefit over a single options array prop — it exists purely so the code reads more nicely
BThe consumer controls the exact structure of each option's JSX — they can freely mix in other elements between options, add a divider, wrap one option in a tooltip, or reorder them — while Select and Select.Option still coordinate shared state (which option is selected) behind the scenes via context, instead of forcing every possible arrangement to be expressed through one rigid, all-configuration-via-props API
CIt forces every consumer to store selected in Redux instead of local component state
DIt's a performance optimization that guarantees Select.Option never re-renders

Both usages above share the exact same MouseTracker. What does the Render Props pattern actually accomplish here?

javascript
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  return (
    <div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
      {render(position)}
    </div>
  );
}

<MouseTracker render={({ x, y }) => <p>Mouse: {x}, {y}</p>} />
<MouseTracker render={({ x, y }) => <Circle left={x} top={y} />} />
AIt renders the component twice, purely for a visual before/after comparison
BIt replaces useState entirely — the render function itself is where the state actually lives
CIt shares reusable, stateful behavior (tracking mouse position) without dictating what gets rendered — MouseTracker owns the tracking logic, and calls render(position) with its current internal state, letting each caller decide independently what UI to produce from that same data
DIt's a deprecated pattern with no valid use cases left in modern React

Why split state and dispatch into two separate contexts here, rather than putting both in one combined { state, dispatch } context value?

javascript
const [state, dispatch] = useReducer(reducer, initialState);

return (
  <DispatchContext.Provider value={dispatch}>
    <StateContext.Provider value={state}>
      {children}
    </StateContext.Provider>
  </DispatchContext.Provider>
);

// A button that only ever dispatches, never reads state:
function AddButton() {
  const dispatch = useContext(DispatchContext);
  return <button onClick={() => dispatch({ type: 'add' })}>Add</button>;
}
AAddButton only ever consumes DispatchContext, and dispatch from useReducer is guaranteed to be a stable reference across renders — so AddButton never re-renders when state changes, since it isn't subscribed to StateContext at all; with a single combined context, any state change would force every consumer (including dispatch-only ones like AddButton) to re-render, since the combined { state, dispatch } object itself would be a new reference every time state changes
BSplitting contexts is required syntax whenever useReducer is combined with Context — React enforces this pairing
CuseReducer automatically batches every dispatched action into a single re-render regardless of how contexts are structured, so the split has no actual effect on re-render counts
DThis split means AddButton needs less code to write than reading from one combined context would require

Sign up free to play

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