Blog

Blog's main page
EN
Picture of the article: An Efficient Way to Consume React Context Values with react-context-selector

An Efficient Way to Consume React Context Values with react-context-selector

React Context is a common solution to avoid prop drilling and a convenient way to share data across multiple components — especially when they all need access to the same values.

However, using context can introduce performance issues: every time the context value changes, all components consuming it with useContext or use() are re-rendered, even if they only use a small part of the data.

This might not be noticeable in small projects or simple interfaces, but on medium to large-scale applications, it can become a real bottleneck.

Let’s walk through a classic React Context setup, and then see how to optimize it using @fluentui/react-context-selector

The source code and a working example can be obtained from this repository. As usual all CSS classes came from TailwindCss

Classic React Context: Creating the Context

Here’s a common way to define and use React Context:

import { Dispatch, PropsWithChildren, SetStateAction, createContext, useState } from 'react';

type StandardContextProviderProps = PropsWithChildren;

interface StandardContextData {
  counter01: number;
  counter02: number;
  setCounter01: Dispatch<SetStateAction<number>>;
  setCounter02: Dispatch<SetStateAction<number>>;
}

export const StandardContext = createContext<StandardContextData | null>(null);

const StandardContextProvider = ({ children }: StandardContextProviderProps) => {
  const [counter01, setCounter01] = useState(0);
  const [counter02, setCounter02] = useState(0);

  return (
    <StandardContext value={{ counter01, counter02, setCounter01, setCounter02 }}>
      {children}
    </StandardContext>
  );
};

export default StandardContextProvider;

Context is created with createContext() ( which returns a context object ), and its value is shared by wrapping components with the Provider.

In React 19, you can now write <MyContext> instead of <MyContext.Provider> — a welcome syntactic simplification.

Since our context is created outside of any component, and we don't yet have meaningful initial values, we initialize it with null. Then, the Provider shares four values: two numeric counters and two setter functions.

Classic React Context: Creating a Custom Hook

To read the context value, we use the useContext() hook — or use() in React 19.
However, for clarity and reusability, let’s wrap it in a custom hook:

import { StandardContext } from '@/app/components/context/StandardContext';
import { use } from 'react';

const useStandardContext = () => {
  const contextValue = use(StandardContext);

  if (!contextValue) {
    throw new Error('useStandardContext must be used within a StandardContextProvider');
  }

  return contextValue;
};

export default useStandardContext;

This check helps avoid consuming context outside the provider — a classic mistake ( that I’ve made a bunch of times ).

Classic React Context: Consuming the Value

Now let’s create two components, Consumer01 and Consumer02, each responsible for displaying and updating a single counter:

import useStandardContext from '@/hooks/useStandardContext';
import StandardContextProvider from '../Context/StandardContext';

const getDateDisplay = () => {
  const now = new Date();
  return now.toLocaleTimeString() + '.' + now.getMilliseconds();
};

const ConsumersWrapper = () => {
  return (
    <StandardContextProvider>
      <div className="flex justify-center items-center gap-4 p-4">
        <Consumer01 />
        <Consumer02 />
      </div>
    </StandardContextProvider>
  );
};

const Consumer01 = () => {
  const { counter01, setCounter01 } = useStandardContext();

  return (
    <div className="flex flex-col gap-4 p-4 rounded-lg bg-slate-800">
      <div className="text-xl">Consumer01</div>
      <div className="text-2xs">{`ConsumersWrapper: ${getDateDisplay()}`}</div>
      <div className="text-lg font-semibold">{counter01}</div>
      <button onClick={() => setCounter01(counter01 + 1)}>+</button>
      <button onClick={() => setCounter01(counter01 - 1)}>-</button>
    </div>
  );
};

const Consumer02 = () => {
  const { counter02, setCounter02 } = useStandardContext();

  return (
    <div className="flex flex-col gap-4 p-4 rounded-lg bg-slate-800">
      <div className="text-xl">Consumer02</div>
      <div className="text-2xs">{`ConsumersWrapper: ${getDateDisplay()}`}</div>
      <div className="text-lg font-semibold">{counter02}</div>
      <button onClick={() => setCounter02(counter02 + 1)}>+</button>
      <button onClick={() => setCounter02(counter02 - 1)}>-</button>
    </div>
  );
};

export default ConsumersWrapper;

Classic React Context: The Problem

Notice what happens: both components re-render whenever either counter is updated — even though each component only uses one of the two values.

This kind of unnecessary re-rendering is fine in small apps, but in larger UIs, it can severely impact performance.

The Fix: react-context-selector

To solve this, we can use @fluentui/react-context-selector

This package allows you to select only the specific part of context that your component depends on — so components only re-render when their part changes.

Let’s refactor our context:

import { createContext } from '@fluentui/react-context-selector';
import { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';

export type EfficientContextProviderProps = PropsWithChildren;

export type EfficientContextData = {
  counter01: number;
  counter02: number;
  setCounter01: Dispatch<SetStateAction<number>>;
  setCounter02: Dispatch<SetStateAction<number>>;
};

// @fluentui/react-context-selector createContext
export const EfficientContext = createContext<EfficientContextData | null>(null);

const EfficientContextProvider = ({ children }: EfficientContextProviderProps) => {
  const [counter01, setCounter01] = useState(0);
  const [counter02, setCounter02] = useState(0);

  // With @fluentui/react-context-selector ".Provider" syntax is required
  return (
    <EfficientContext.Provider value={{ counter01, counter02, setCounter01, setCounter02 }}>
      {children}
    </EfficientContext.Provider>
  );
};

export default EfficientContextProvider;

Note: Even with React 19’s new <Context> syntax, you must still use .Provider when working with react-context-selector ( as i write these lines )

Updating the Custom Hook

Now, update the hook to use useContextSelector instead of useContext or use():

import { EfficientContext, EfficientContextData } from '@/app/components/context/EfficientContext';
import { ContextSelector, useContextSelector } from '@fluentui/react-context-selector';

const useEfficientContext = <T>(selector: ContextSelector<EfficientContextData, T>) => {
    return useContextSelector(EfficientContext, (context) => {
        if (!context) {
            throw new Error('useEfficientContext must be wrapped in a EfficientContextProvider');
        }

        return selector(context);
    });
};

export default useEfficientContext;

According to the docs, this hook isn’t strictly required, but it adds a testable layer and cleaner usage.

Consuming with react-context-selector

Here’s how to use the new context and custom hook in your components:

import useEfficientContext from '@/hooks/useEfficientContext';
import EfficientContextProvider from '../Context/EfficientContext';

const getDateDisplay = () => {
  const now = new Date();
  return now.toLocaleTimeString() + '.' + now.getMilliseconds();
};

const ConsumersWrapper = () => {
  return (
    <EfficientContextProvider>
      <div className="flex justify-center items-center gap-4 p-4">
        <Consumer01 />
        <Consumer02 />
      </div>
    </EfficientContextProvider>
  );
};

const Consumer01 = () => {
  const counter01 = useEfficientContext((ctx) => ctx.counter01);
  const setCounter01 = useEfficientContext((ctx) => ctx.setCounter01);

  return (
    <div className="flex flex-col gap-4 p-4 rounded-lg bg-slate-800">
      <div className="text-xl">Consumer01</div>
      <div className="text-2xs">{`ConsumersWrapper: ${getDateDisplay()}`}</div>
      <div className="text-lg font-semibold">{counter01}</div>
      <button onClick={() => setCounter01(counter01 + 1)}>+</button>
      <button onClick={() => setCounter01(counter01 - 1)}>-</button>
    </div>
  );
};

const Consumer02 = () => {
  const counter02 = useEfficientContext((ctx) => ctx.counter02);
  const setCounter02 = useEfficientContext((ctx) => ctx.setCounter02);

  return (
    <div className="flex flex-col gap-4 p-4 rounded-lg bg-slate-800">
      <div className="text-xl">Consumer02</div>
      <div className="text-2xs">{`ConsumersWrapper: ${getDateDisplay()}`}</div>
      <div className="text-lg font-semibold">{counter02}</div>
      <button onClick={() => setCounter02(counter02 + 1)}>+</button>
      <button onClick={() => setCounter02(counter02 - 1)}>-</button>
    </div>
  );
};

export default ConsumersWrapper;

Heads-up: Don’t Destructure the Whole Context

If you destructure the entire context, you bypass the selector optimization — defeating the purpose of using react-context-selector.

// ❌ Don’t do this
const { counter01, setCounter01 } = useEfficientContext((ctx) => ctx);

// ❌ Still wrong — subscribes to the full context
const { counter01 } = useContextSelector(EfficientContext, (ctx) => ctx);

// ✅ Correct
const counter01 = useEfficientContext((ctx) => ctx.counter01);
// ...or
const counter01 = useEfficientContext(({ counter01 }) => counter01);

Conclusion

You now have a more efficient way to consume context in React apps using react-context-selector. It’s especially helpful in performance-sensitive parts of your UI where you want precise control over re-renders.