Back to all notes

Context Selector : Subscribe to Just What You Need

Use use-context-selector to avoid re-renders by selecting slices of context.

2 min read

Plain useContext has a blunt contract: change any field of the value and every consumer re-renders. Most of my components read exactly one field. A context selector lets a component subscribe to just that slice, so it re-renders only when the selected slice changes.

One field changes, every consumer re-renders

Two components under one provider. NameComponent reads name, CountComponent reads count:

import { createContext, useContext, useState } from "react";

const AppContext = createContext();

function NameComponent() {
  const { name } = useContext(AppContext);
  return <p>Name: {name}</p>;
}

function CountComponent() {
  const { count } = useContext(AppContext);
  return <p>Count: {count}</p>;
}

function App() {
  const [name, setName] = useState("John");
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Context Selector</h1>
      <AppContext.Provider value={{ name, count }}>
        <NameComponent />
        <CountComponent />
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </AppContext.Provider>
    </div>
  );
}

export default App;

Both get the whole context object from useContext. Press Increment and NameComponent re-renders too, even though it only ever needed name.

A selector per component

Select name and count independently:

import { createContext, useContext, useState, useMemo } from "react";

const AppContext = createContext();

// Simplified useContextSelector implementation
function useContextSelector(context, selector) {
  const value = useContext(context);
  return useMemo(() => selector(value), [value, selector]);
}

function NameComponent() {
  const name = useContextSelector(AppContext, (v) => v.name);
  return <p>Name: {name}</p>;
}

function CountComponent() {
  const count = useContextSelector(AppContext, (v) => v.count);
  return <p>Count: {count}</p>;
}

function App() {
  const [name, setName] = useState("John");
  const [count, setCount] = useState(0);
  const contextValue = useMemo(() => ({ name, count }), [name, count]);

  return (
    <div>
      <h1>Context Selector</h1>
      <AppContext.Provider value={contextValue}>
        <NameComponent />
        <CountComponent />
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </AppContext.Provider>
    </div>
  );
}

export default App;

Each consumer now picks its field with a selector like (v) => v.name. The rest is hygiene: keep the provider value stable, memoizing complex objects, then verify that unrelated updates actually stopped re-rendering the component.

Selectors that return primitives, or references that stay stable between renders, cause the least churn. And measure before reaching for this. It’s worth it where re-renders are actually costly.