DEV Community

Cover image for Prevent React Context Re-renders with Redux-Style Selectors
Hussnain Hashmi
Hussnain Hashmi

Posted on

Prevent React Context Re-renders with Redux-Style Selectors

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, when you use this context in a component:

function UserProfile() {
  const { user } = useContext(AppContext);

  return <div>Hello, {user?.name}!</div>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

2. Array Selector (Multiple Values)

const { user, theme } = useContextHook(AppContext, ['user', 'theme']);
Enter fullscreen mode Exit fullscreen mode

3. Object Selector (Explicit Picks)

const { user, theme } = useContextHook(AppContext, { 
  user: 1, 
  theme: 1 
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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:

  1. When you call useContextHook with a selector, it registers a listener for that specific component
  2. When the context value changes, the library runs your selector on both the old and new values
  3. It performs a deep comparison of the results
  4. 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']);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Live Examples

I've created four CodeSandbox examples showing different selector patterns:

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)