Skip to content

Feature request: useContextGetterΒ #21329

Closed as not planned
Closed as not planned
@maclockard

Description

Right now the only hook for consuming a React context is useContext, which is great for most cases. However, one downside is that it results in a component re-rendering whether or not the context itself is directly used for displaying something. Take the following example:

export const ExpensiveComponent = React.memo(function ExpensiveComponent() {
  const myContext = useContext(MyContext);

  const onClick = useCallback(() => {
    doThing(myContext);
  }, [myContext]);

  // lots of other hooks

  return (
    <div>
      <button onClick={onClick}>Click Me</button>
      {/* ...other children... */}
    </div>
  );
});

Here the value of MyContext is only used when onClick is called, it is not used by any returned DOM elements or child components. However, if the value of myContext changes, ExpensiveComponent will re-render despite no differences in what is being displayed.

One way to prevent this component from over re-rendering would be to provide a hook along the lines of useContextGetter. It would prevent ExpensiveComponent from re-rendering by returning a getter function for MyContext that would allow onClick to lazily access the current context's value. This getter would be a stable function similar to the callback useState returns.

Here's the above example rewritten to use useContextGetter:

export const ExpensiveComponent = React.memo(function ExpensiveComponent() {
  const getMyContext = useContextGetter(MyContext);

  const onClick = useCallback(() => {
    doThing(getMyContext());
  }, [getMyContext]);

  // lots of other hooks

  return (
    <div>
      <button onClick={onClick}>Click Me</button>
      {/* ...other children... */}
    </div>
  );
});

There is some prior art for an API similar to this with Recoil's useRecoilCallback making it possible to access Recoil state inside of a callback without requiring a component to re-render when the state changes. One could also construct similar functionality with React Redux's useStore and calling getState() on the store inside of a callback.

The above examples I used are pretty trivial and one could simply refactor the part that uses MyContext into a separate child component to avoid re-rendering ExpensiveComponent. However, its not difficult to imagine a scenario where such a refactor may be challenging or a component being used in enough places that the re-render causes performance degradation.

Metadata

Assignees

No one assigned

    Labels

    Resolution: StaleAutomatically closed due to inactivityStatus: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions