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 withreact-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.