Prevent Unnecessary Re-renders from Unstable Values in React

Flags React context values, memo props, and effect dependencies recreated on every render, which re-render large trees or rerun effects.

What it flags

Flaggedproviders/SessionProvider.tsx
import { createContext, useState, type ReactNode } from 'react';
import type { User } from './types';

type Session = { user: User | null; signOut: () => void };
type Props = { initialUser: User | null; children: ReactNode };

export const SessionContext = createContext<Session | null>(null);

export function SessionProvider({ initialUser, children }: Props) {
  const [user, setUser] = useState(initialUser);

  // A new value object on every render re-renders every consumer
  return (
    <SessionContext value={{ user, signOut: () => setUser(null) }}>
      {children}
    </SessionContext>
  );
}
Passesproviders/SessionProvider.tsx
import { createContext, useCallback, useMemo, useState, type ReactNode } from 'react';
import type { User } from './types';

type Session = { user: User | null; signOut: () => void };
type Props = { initialUser: User | null; children: ReactNode };

export const SessionContext = createContext<Session | null>(null);

export function SessionProvider({ initialUser, children }: Props) {
  const [user, setUser] = useState(initialUser);
  const signOut = useCallback(() => setUser(null), []);
  const session = useMemo(() => ({ user, signOut }), [user, signOut]);

  return <SessionContext value={session}>{children}</SessionContext>;
}

Why it matters

React re-renders every component that reads a context when the provider's value changes identity, so an object literal in an app-level provider re-renders every consumer each time the provider renders. A new inline object or callback passed to a memo component fails its prop comparison, so the component pays for memoization and still re-renders. A callback recreated in a parent and listed in a child's effect dependencies reruns that effect, and any fetch or subscription in it, on every parent render. The cost grows with the tree and shows up as input lag long after the change merged.

Agent instructions

cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.

AGENTS.md

Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.

## Prevent Unnecessary Re-renders from Unstable Values in React

Applies to `**/*.tsx`, `**/*.jsx`.

Review React context providers, memoized components, and effect dependencies for values that get a new identity on every render.

Flag changes that:
- Pass an object or array literal, or an inline function, as the value of a context provider consumed across much of the tree (auth, theme, app state, data clients) without useMemo or useCallback.
- Put fast-changing state, such as input text, pointer position, or a ticking timer, in the same context value as slow-changing data, so every consumer re-renders on each change; split the context instead.
- Pass new object, array, or inline function props to a component wrapped in memo, which defeats its shallow prop comparison.
- Pass a new object or function from a parent to a child that lists it as a useEffect dependency, rerunning the child's effect, and any fetch or subscription in it, on every parent render.

Do not flag:
- Code compiled by React Compiler (babel-plugin-react-compiler, or a framework setting such as reactCompiler in next.config), which memoizes values and callbacks automatically, unless the component opts out with "use no memo".
- Inline objects or callbacks passed to DOM elements or to components that are not memoized and are cheap to render.
- Primitive context values, or providers near the leaves with a handful of consumers.

Checks files matching **/*.tsx, **/*.jsx.

More React agents

See all React code review standards

Run this agent on every pull request

cubic reviews every pull request against the agents you turn on and comments on the lines that break them.

Catch this in your code