Prevent React Context Re-renders with Redux-Style Selectors
Have you ever noticed your React app slowing down because components re-render unnecessarily when using Context? You're not alone. The default useContext hook has a performance problem: every component using the context re-renders whenever ANY value in that context changes.
Today, I'm excited to share use-context-hook - a tiny library (just 2KB!) that solves this problem with Redux-style selectors for React Context.
The Problem
Let's say you have a context with multiple values:
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme, notifications, setNotifications }}>
{children}
</AppContext.Provider>
);
}
Now, when you use this context in a component:
function UserProfile() {
const { user } = useContext(AppContext);
return <div>Hello, {user?.name}!</div>;
}
The problem: This component re-renders when theme changes, when notifications change, or when ANY value in the context changes - even though it only cares about user!
The Solution
With use-context-hook, you can subscribe to only the values you need:
import { createContextHook, useContextHook } from 'use-context-hook';
// Create context
const AppContext = createContextHook();
// Provider stays the same
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme, notifications, setNotifications }}>
{children}
</AppContext.Provider>
);
}
// Component ONLY re-renders when 'user' changes
function UserProfile() {
const user = useContextHook(AppContext, 'user');
return <div>Hello, {user?.name}!</div>;
}
That's it! Now UserProfile only re-renders when user changes, not when theme or notifications change.
Four Flexible Selector Patterns
The library supports multiple selector patterns to fit your coding style:
1. String Selector (Single Value)
const user = useContextHook(AppContext, 'user');
2. Array Selector (Multiple Values)
const { user, theme } = useContextHook(AppContext, ['user', 'theme']);
3. Object Selector (Explicit Picks)
const { user, theme } = useContextHook(AppContext, {
user: 1,
theme: 1
});
4. Function Selector (Redux-Style)
const { user, theme } = useContextHook(AppContext, (state) => ({
user: state.user,
theme: state.theme
}));
// Or with transformations
const userName = useContextHook(AppContext, (state) => state.user?.name);
Real-World Performance Impact
I've tested this in production apps with large context objects, and the results are impressive:
- Before: 50+ unnecessary re-renders per user interaction
- After: Only the 2-3 components that actually need to update
In one case, we reduced a form's re-render count from 47 to just 3 when a user typed in a field!
How It Works (The Technical Bits)
The library uses a clever listener pattern:
- When you call
useContextHookwith a selector, it registers a listener for that specific component - When the context value changes, the library runs your selector on both the old and new values
- It performs a deep comparison of the results
- Only if the selected values changed does it trigger a re-render
This means zero re-renders for unchanged data, even with deeply nested objects!
TypeScript Support
Full TypeScript support with type inference:
interface AppContextType {
user: User | null;
theme: 'light' | 'dark';
notifications: Notification[];
}
const AppContext = createContextHook<AppContextType>();
// Type is inferred as User | null
const user = useContextHook(AppContext, 'user');
// Type is inferred as { user: User | null; theme: 'light' | 'dark' }
const { user, theme } = useContextHook(AppContext, ['user', 'theme']);
When Should You Use This?
Use use-context-hook when:
- You have a large context object with multiple values
- Components only need specific parts of the context
- You're experiencing performance issues with standard Context
- You want Redux-like selector patterns without Redux overhead
Don't use it when:
- Your context is tiny (1-2 values)
- All components need all context values anyway
- You're already using Redux or Zustand (which have their own solutions)
Installation
npm install use-context-hook
# or
yarn add use-context-hook
# or
pnpm add use-context-hook
Live Examples
I've created four CodeSandbox examples showing different selector patterns:
- String Selector Example
- Array Selector Example
- Object Selector Example
- Redux-Style Function Selector
Comparison with Alternatives
| Library | Bundle Size | Selector Patterns | TypeScript | Re-render Prevention |
|---|---|---|---|---|
| use-context-hook | ~2KB | 4 patterns | Full support | Yes |
| use-context-selector | ~3KB | Function only | Partial | Yes |
| React Context | 0KB (built-in) | None | Built-in | No |
| Redux | ~15KB+ | Yes | Yes | Yes (but heavyweight) |
Wrapping Up
React Context is powerful, but its default behavior can cause performance issues in larger apps. use-context-hook gives you Redux-style selective subscriptions without the Redux complexity.
The library is:
- Tiny - Just 2KB minified
- Fast - Deep comparison only on selected values
- Type-safe - Full TypeScript support
- Flexible - Four selector patterns to choose from
- Zero dependencies - Only React as a peer dependency
Links
Have you faced re-render issues with React Context? How did you solve them? Let me know in the comments!
If you found this helpful, give the repo a star on GitHub!
Top comments (0)