Frontend Engineering
React Rendering Performance
Quiz: Re-render Prediction
advanced13 min read
Test Your Re-render Intuition
You've made it through re-render triggers, memo, composition, context, and transitions. Now let's see if you can actually predict what React does under pressure. No guessing here -- you need to trace through the logic step by step.
Scenario 1: The Prop Myth
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+</button>
<Child name="Alice" />
</div>
);
}
function Child({ name }) {
console.log('Child rendered');
return <p>{name}</p>;
}
Quiz
Scenario 2: memo With Inline Object
const MemoChild = memo(function Child({ style, label }) {
console.log('Child rendered');
return <p style={style}>{label}</p>;
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<MemoChild style={{ color: 'blue' }} label="Hello" />
</div>
);
}
Quiz
Scenario 3: Children as Props
function Wrapper({ children }) {
const [expanded, setExpanded] = useState(false);
console.log('Wrapper rendered');
return (
<div>
<button onClick={() => setExpanded(e => !e)}>Toggle</button>
{expanded && <div className="details">Details panel</div>}
{children}
</div>
);
}
function App() {
return (
<Wrapper>
<ExpensiveComponent />
</Wrapper>
);
}
function ExpensiveComponent() {
console.log('ExpensiveComponent rendered');
return <div>Heavy content</div>;
}
Quiz
Scenario 4: Context Cascade
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Header />
<Content />
<ThemeToggle onToggle={() => setTheme(t => t === 'light' ? 'dark' : 'light')} />
</ThemeContext.Provider>
);
}
const Header = memo(function Header() {
console.log('Header rendered');
return <h1>My App</h1>;
});
function Content() {
const theme = useContext(ThemeContext);
console.log('Content rendered');
return <div className={theme}>Content</div>;
}
const ThemeToggle = memo(function ThemeToggle({ onToggle }) {
console.log('ThemeToggle rendered');
return <button onClick={onToggle}>Toggle Theme</button>;
});
Quiz
Scenario 5: Same Value setState
function Counter() {
const [count, setCount] = useState(0);
console.log('Counter rendered');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(0)}>Set to 0</button>
</div>
);
}
Quiz
Scenario 6: useTransition and Interruption
function App() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
console.log('App rendered, tab:', tab, 'pending:', isPending);
return (
<div>
<button onClick={() => startTransition(() => setTab('about'))}>
Go to About
</button>
<button onClick={() => setTab('home')}>
Go Home (urgent)
</button>
<TabContent tab={tab} />
</div>
);
}
function TabContent({ tab }) {
// Simulates expensive render (200ms)
console.log('TabContent rendered:', tab);
return <div>{tab} content</div>;
}
Quiz
Scenario 7: useMemo and Render Scope
function ProductList({ products, category }) {
const filtered = useMemo(
() => products.filter(p => p.category === category),
[products, category]
);
console.log('ProductList rendered');
return filtered.map(p => <ProductCard key={p.id} product={p} />);
}
function ProductCard({ product }) {
console.log('ProductCard rendered:', product.name);
return <div>{product.name}</div>;
}
Quiz
Scenario 8: The Full Picture
const UserContext = createContext(null);
const Header = memo(function Header() {
const user = useContext(UserContext);
console.log('Header rendered');
return <h1>Welcome, {user.name}</h1>;
});
const Sidebar = memo(function Sidebar() {
console.log('Sidebar rendered');
return <nav>Sidebar</nav>;
});
function MainContent({ children }) {
const [count, setCount] = useState(0);
console.log('MainContent rendered');
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{children}
</div>
);
}
function App() {
const [user, setUser] = useState({ name: 'Alice' });
return (
<UserContext.Provider value={user}>
<Header />
<MainContent>
<Sidebar />
</MainContent>
</UserContext.Provider>
);
}
Quiz
Key Takeaways
Key Rules
- 1Parent re-rendering causes ALL children to re-render by default. This is the most common source of wasted renders.
- 2React.memo adds a prop comparison gate. But one unstable prop (inline object or function) defeats the entire memo.
- 3Children-as-props is a powerful optimization: elements created by a non-re-rendering parent have stable references.
- 4Context changes bypass memo and re-render all consumers. Split contexts by update frequency.
- 5useMemo caches computation results but doesn't prevent the component from re-rendering. It prevents downstream work.
- 6useTransition makes renders interruptible. Interrupted transitions are discarded — the user never sees half-rendered content.
- 7Same-value setState (Object.is match) triggers a bailout. React may still call the component once but won't render children or commit.
- 8Always trace the re-render chain: who called setState? → who is the parent of each component? → is memo/composition blocking the cascade?