Minimizing Shared State with React Hooks: The Wormhole State Management Pattern

This article explains how to keep state as close as possible to where it is used, using props for simple sharing, React Context as a "wormhole" for widely needed state, and custom hooks to simplify the pattern, illustrated with a step‑by‑step click‑counter example and performance considerations.

Hacker Afternoon Tea
Hacker Afternoon Tea
Hacker Afternoon Tea
Minimizing Shared State with React Hooks: The Wormhole State Management Pattern

Wormhole State Management Pattern

State should be kept as close to its usage as possible. If a single component needs the state, pass it via props. If several components need it, still use props. When many components need the same data, place it in a React Context, which acts like a wormhole that bends the component tree so distant parts can communicate. Custom hooks simplify consuming the context.

Example: Click Counter

Step 1 – useState

const ClickCounter = () => {
  const [count, setCount] = useState(0);
  const onClick = () => setCount(c => c + 1);
  return <button onClick={onClick}>{count} +1</button>;
};

State count holds the click number; setCount updates it.

Step 2 – Reusable button

const ClickCounter = () => {
  const [count, setCount] = useState(0);
  const onClick = () => setCount(c => c + 1);
  return (
    <>
      <p>You have clicked buttons {count} times</p>
      <PrettyButton onClick={onClick}>+1</PrettyButton>
    </>
  );
};

The state remains in ClickCounter while UI uses a shared PrettyButton component.

Step 3 – Grouped state

const ClickCounter = () => {
  const [count, setCount] = useState({A: 0, B: 0});
  const onClickA = () => setCount(c => ({...c, A: c.A + 1}));
  const onClickB = () => setCount(c => ({...c, B: c.B + 1}));
  return (
    <>
      <p>You have clicked A: {count.A}, B: {count.B} times</p>
      <PrettyButton onClick={onClickA}>A +1</PrettyButton>
      <PrettyButton onClick={onClickB}>B +1</PrettyButton>
    </>
  );
};

State is now an object {A, B}, allowing a single state to hold multiple values. React re‑renders by shallowly comparing the whole state object; performance degrades around 10 000 elements.

For more complex updates, useReducer is recommended:

const [state, dispatch] = useReducer((state, action) => {
  switch (action.type) {
    case 'A': return {...state, A: state.A + 1};
    case 'B': return {...state, B: state.B + 1};
    default: return state;
  }
}, {A: 0, B: 0});

Step 4 – Prop‑drilling problem

const AlternativeClick = ({count, setCount}) => {
  const onClick = () => setCount(c => ({...c, B: c.B + 1}));
  return (
    <div>
      You can also update B here
      <PrettyButton onClick={onClick}>B +1</PrettyButton>
      <p>It's {count.B} btw</p>
    </div>
  );
};

Passing count and setCount through many layers makes the component hard to move and understand.

Step 5 – Wormhole with custom hook

Define a context provider that stores a rich state object and a setter that updates a specific key:

export const SharedCountProvider = ({children}) => {
  const [state, setState] = useState({A: 0, B: 0});
  const [contextValue, setContextValue] = useState({
    state,
    setSharedCount: (key, val) => {
      setState(s => ({...s, [key]: val}));
    },
  });

  useEffect(() => {
    setContextValue(cv => ({...cv, state}));
  }, [state]);

  return (
    <SharedCountContext.Provider value={contextValue}>
      {children}
    </SharedCountContext.Provider>
  );
};

Consume the context via a custom hook:

export function useSharedCount() {
  const {state, setSharedCount} = useContext(SharedCountContext);
  const incA = () => setSharedCount('A', state.A + 1);
  const incB = () => setSharedCount('B', state.B + 1);
  return {count: state, incA, incB};
}

Components can now access shared state without prop‑drilling:

const AlternativeClick = () => {
  const {count, incB} = useSharedCount();
  return (
    <div>
      You can also update B here
      <PrettyButton onClick={incB}>B +1</PrettyButton>
      <p>It's {count.B} btw</p>
    </div>
  );
};

Performance considerations

Keep the shared state minimal and use separate context providers for different parts of the app. Avoid making the context global unless required; wrap only the smallest necessary subtree.

Complexity

Maintain low complexity; do not add unnecessary features.

Alternative implementations

The provider logic can be replaced with useReducer, XState, or Redux if those libraries better suit the project.

Real‑world usage (Sentry)

Sentry’s codebase includes organizationContext.tsx, which follows this pattern:

https://github.com/getsentry/sentry/blob/master/static/app/views/organizationContext.tsx

References

https://swizec.com/blog/wormhole-state-management/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

State ManagementReActHooksuseReducercontextuseSharedCountWormhole Pattern
Hacker Afternoon Tea
Written by

Hacker Afternoon Tea

You might find something interesting here ^_^

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.