Harness-engineering state-context-pattern

React Context Pattern

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/state-context-pattern" ~/.claude/skills/intense-visions-harness-engineering-state-context-pattern-1e3e05 && rm -rf "$T"
manifest: agents/skills/claude-code/state-context-pattern/SKILL.md
source content

React Context Pattern

Manage shared state with React Context and useReducer for prop-drilling avoidance and scoped state

When to Use

  • Sharing state across a component subtree without passing props through intermediate components
  • Theme, locale, auth, or feature flag state that many components read but rarely changes
  • Scoped state that should reset when a part of the tree unmounts
  • Small applications where adding Zustand/Jotai/Redux is unnecessary overhead

Instructions

  1. Create a context with
    createContext
    . Provide a meaningful default or
    null
    with a type assertion.
  2. Create a Provider component that encapsulates state logic (useState or useReducer) and passes values via
    value
    .
  3. Create a custom hook (
    useAuth
    ,
    useTheme
    ) that calls
    useContext
    and throws if used outside the Provider.
  4. Split context into two: one for state, one for dispatch/actions. This prevents components that only dispatch from re-rendering on state changes.
  5. Memoize the context value with
    useMemo
    to prevent unnecessary re-renders of consumers.
  6. Keep context values small and stable — large objects that change frequently cause all consumers to re-render.
// contexts/auth-context.tsx
import { createContext, useContext, useReducer, useMemo, ReactNode } from 'react';

interface User { id: string; name: string; }

interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
}

type AuthAction =
  | { type: 'LOGIN'; user: User }
  | { type: 'LOGOUT' };

type AuthDispatch = (action: AuthAction) => void;

const AuthStateContext = createContext<AuthState | null>(null);
const AuthDispatchContext = createContext<AuthDispatch | null>(null);

function authReducer(state: AuthState, action: AuthAction): AuthState {
  switch (action.type) {
    case 'LOGIN':
      return { user: action.user, isAuthenticated: true };
    case 'LOGOUT':
      return { user: null, isAuthenticated: false };
    default:
      return state;
  }
}

export function AuthProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    isAuthenticated: false,
  });

  // Memoize to prevent re-renders when AuthProvider's parent re-renders
  const stateValue = useMemo(() => state, [state]);

  return (
    <AuthStateContext.Provider value={stateValue}>
      <AuthDispatchContext.Provider value={dispatch}>
        {children}
      </AuthDispatchContext.Provider>
    </AuthStateContext.Provider>
  );
}

export function useAuthState(): AuthState {
  const context = useContext(AuthStateContext);
  if (!context) throw new Error('useAuthState must be used within AuthProvider');
  return context;
}

export function useAuthDispatch(): AuthDispatch {
  const context = useContext(AuthDispatchContext);
  if (!context) throw new Error('useAuthDispatch must be used within AuthProvider');
  return context;
}
// Usage
function LoginButton() {
  const dispatch = useAuthDispatch(); // Does NOT re-render when auth state changes
  return <button onClick={() => dispatch({ type: 'LOGIN', user: { id: '1', name: 'Alice' } })}>Login</button>;
}

function UserBadge() {
  const { user } = useAuthState(); // Re-renders when auth state changes
  return user ? <span>{user.name}</span> : null;
}

Details

Why split state and dispatch contexts: When the context value is

{ state, dispatch }
, every consumer re-renders when state changes — even components that only call dispatch. Separate contexts solve this.

Context vs external state libraries:

  • Context re-renders ALL consumers when the value changes — no selector mechanism
  • Context is scoped to a subtree — external stores are global
  • Context requires no extra dependencies — it is built into React
  • Context is best for low-frequency updates (auth, theme). For high-frequency updates (forms, animations), use Zustand or Jotai

useReducer vs useState: Use

useReducer
when state has multiple sub-values, when the next state depends on the previous state, or when you want to decouple state logic from the Provider component.

Performance optimization: For large subtrees, wrap children in

React.memo
or use the split-context pattern. For truly high-performance needs, switch to an external store.

Testing:

function renderWithAuth(ui: ReactNode, initialState?: Partial<AuthState>) {
  return render(<AuthProvider>{ui}</AuthProvider>);
}

Common mistakes:

  • Putting too much in context (entire app state) — this defeats React's component-level rendering model
  • Creating context with an empty object default (
    createContext({})
    ) — hides bugs where the provider is missing
  • Not memoizing the context value — a new object on every render causes all consumers to re-render

Source

https://react.dev/learn/passing-data-deeply-with-context

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.