Mesrai
Back to blog
// essayTechnical Deep Dive

Memory Leak in a React Context That Never Garbage-Collects

Real PR catch: Context Provider with inline-literal value — every consumer re-renders, memory pressure. useMemo + useCallback fix.

Mesrai TeamAugust 29, 20268 min read

An Auth Provider passing `{ user, logout, settings }` as inline object. Every render creates a new object reference; React triggers re-render on every consumer. Performance degrades as the app grows.

The vulnerable diff

tsxcontext/Auth.tsx
// context/Auth.tsx
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [settings, setSettings] = useState<Settings | null>(null);
  const logout = () => setUser(null);

  // BUG: new object every render → all consumers re-render
  return (
    <AuthContext.Provider value={{ user, logout, settings }}>
      {children}
    </AuthContext.Provider>
  );
}

What is wrong

React's context propagation triggers re-render of every consumer whenever the Provider's `value` changes by reference. An inline object literal creates a new reference on every render of the Provider's parent — even if `user`, `logout`, and `settings` haven't changed. Every consumer re-renders unnecessarily. The fix is `useMemo` for the value (preserving reference when dependencies are unchanged), or splitting into multiple contexts so unrelated consumers don't re-render together.

The attack

Performance impact:

textexploit
100 consumers under <AuthProvider>.
Parent re-renders 60 times/sec (e.g., parent has a setState somewhere).
Without memo: 60 * 100 = 6000 unnecessary re-renders/sec.
With memo: 0 (until user/logout/settings change).

Common cause of mysterious React performance issues.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 1 min ago

[mesrai] [code-review] [Performance] [React-Context-Re-render] [medium]

`value={{ ... }}` creates a new object every render. All consumers
re-render every time the Provider's parent renders.

useMemo the value:

  const value = useMemo(
    () => ({ user, logout, settings }),
    [user, logout, settings]
  );
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;

Also useCallback for functions inside the value:
  const logout = useCallback(() => setUser(null), []);

For high-traffic contexts, split: separate Provider for state vs
actions so action-only consumers don't re-render on state changes:

  <UserStateContext.Provider value={user}>
    <UserActionsContext.Provider value={actions}>
      {children}

The fix

tsxcontext/Auth.tsx (fixed)
// context/Auth.tsx — fixed
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [settings, setSettings] = useState<Settings | null>(null);

  const logout = useCallback(() => setUser(null), []);
  const value = useMemo(
    () => ({ user, logout, settings }),
    [user, logout, settings]
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

Memoized value preserves reference. useCallback for the function. Re-renders only when actual data changes.

Why human review missed it

Inline Provider value is one of the most common React performance bugs. Mesrai catches every Provider with an inline-literal value.

Related rules + further reading

Mesrai rule pack: performance/react-context-value-memo — flags inline object/array on Context.Provider value.

React docs: useMemo + useCallback for context values.

Common React perf issue.

Takeaway

useMemo Provider values. useCallback functions inside. Mesrai catches the inline pattern.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.