Avoid re-renders

2 snippets in React

REReact

Avoiding Unnecessary Re-renders

RE · useContext
syntax
// Memoize the value object
const value = useMemo(() => ({ data, handler }), [data]);

// Split read-only data from dispatch
<DataContext.Provider value={data}>
  <DispatchContext.Provider value={dispatch}>
example
import { createContext, useContext, useMemo, useState } from 'react';

const CountContext = createContext(null);
const CountDispatchContext = createContext(null);

function CountProvider({ children }) {
  const [count, setCount] = useState(0);

  // dispatch never changes identity
  const increment = useMemo(
    () => () => setCount(c => c + 1),
    []
  );

  return (
    <CountContext.Provider value={count}>
      <CountDispatchContext.Provider value={increment}>
        {children}
      </CountDispatchContext.Provider>
    </CountContext.Provider>
  );
}

// Components reading only dispatch won't re-render when count changes
function IncrementButton() {
  const increment = useContext(CountDispatchContext);
  return <button onClick={increment}>+1</button>;
}

Note Splitting context into a data context and a dispatch context is a powerful pattern. Components that only call actions (dispatch) will not re-render when the data changes, because the dispatch reference stays stable.

Avoiding Unnecessary Renders

RE · Performance
syntax
// 1. Move state down (closer to where it is used)
// 2. Lift content up (pass components as children)
// 3. Memoize with React.memo + useCallback/useMemo
example
// PROBLEM: typing in input re-renders the expensive list
function BadPage() {
  const [query, setQuery] = useState('');
  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <ExpensiveList /> {/* re-renders on every keystroke */}
    </div>
  );
}

// FIX 1: Move state down into its own component
function GoodPage() {
  return (
    <div>
      <SearchInput /> {/* state lives here now */}
      <ExpensiveList /> {/* no longer re-renders on keystrokes */}
    </div>
  );
}

function SearchInput() {
  const [query, setQuery] = useState('');
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

// FIX 2: Lift content up via children
function InputWrapper({ children }) {
  const [query, setQuery] = useState('');
  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {children} {/* children don't re-render */}
    </div>
  );
}

function Page() {
  return (
    <InputWrapper>
      <ExpensiveList />
    </InputWrapper>
  );
}

Note Before reaching for React.memo, try restructuring your component tree. Moving state down and lifting content up are free optimizations that often eliminate the problem entirely.