
React
- 153 installs
- 24 repo stars
- Updated June 10, 2026
- hairyf/skills
Scaffold and extend React UIs with components, hooks, routing, and state patterns aligned to hairyf project conventions for web applications.
About
react skill from hairyf/skills helps agents build modern web interfaces with React, covering components, hooks, routing, and project-specific conventions for maintainable SaaS and content apps.
- Component architecture
- Hooks and state
- Routing patterns
- SSR/CSR awareness
- hairyf React conventions
React by the numbers
- 153 all-time installs (skills.sh)
- Ranked #949 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/skills --skill reactAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 153 |
|---|---|
| repo stars | ★ 24 |
| Last updated | June 10, 2026 |
| Repository | hairyf/skills ↗ |
What it does
Scaffold and extend React UIs with components, hooks, routing, and state patterns aligned to hairyf project conventions for web applications.
Files
React
The skill is based on React, generated at 2026-01-31.
React is a JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called "components". React uses a declarative paradigm that makes it easier to reason about your application and aims to be both efficient and flexible.
Core References
| Topic | Description | Reference |
|---|---|---|
| useState | Hook for managing component state with direct updates | core-usestate |
| useEffect | Hook for synchronizing components with external systems | core-useeffect |
| useContext | Hook for reading and subscribing to context | core-usecontext |
| useRef | Hook for referencing values that don't trigger re-renders | core-useref |
| useReducer | Hook for managing complex state with a reducer function | core-usereducer |
| Suspense | Component for displaying fallback UI while content is loading | core-suspense |
| memo | Higher-order component for memoizing component renders | core-memo |
| createContext | API for creating context objects | core-createcontext |
| Fragment | Component for grouping elements without wrapper nodes | core-fragment |
| StrictMode | Component for enabling additional development checks | core-strictmode |
Features
Performance Optimization
| Topic | Description | Reference |
|---|---|---|
| useMemo | Hook for caching expensive calculations | features-usememo |
| useCallback | Hook for caching function definitions | features-usecallback |
| lazy | API for code splitting and lazy loading components | features-lazy |
| useTransition | Hook for non-blocking state updates | features-usetransition |
| useDeferredValue | Hook for deferring non-critical UI updates | features-usedeferredvalue |
| useLayoutEffect | Hook that fires synchronously before browser repaint | features-uselayouteffect |
| startTransition | API for marking non-blocking state updates | features-starttransition |
Advanced Hooks
| Topic | Description | Reference |
|---|---|---|
| useId | Hook for generating unique IDs for accessibility | features-useid |
| use | API for reading Promise and Context values | features-use |
| useActionState | Hook for managing form action state | features-useactionstate |
| useOptimistic | Hook for optimistic UI updates | features-useoptimistic |
| useInsertionEffect | Hook for CSS-in-JS libraries to inject styles | advanced-useinsertioneffect |
| useSyncExternalStore | Hook for subscribing to external stores | advanced-usesyncexternalstore |
| useImperativeHandle | Hook for customizing ref handles | advanced-useimperativehandle |
| useEffectEvent | Hook for extracting non-reactive logic from Effects | advanced-useeffectevent |
| useDebugValue | Hook for adding labels to custom hooks in DevTools | advanced-usedebugvalue |
React DOM APIs
| Topic | Description | Reference |
|---|---|---|
| createRoot | API for creating a root to render React components | react-dom-createroot |
| hydrateRoot | API for hydrating server-rendered HTML | react-dom-hydrateroot |
| createPortal | API for rendering children into different DOM nodes | react-dom-createportal |
| flushSync | API for forcing synchronous updates | react-dom-flushsync |
React Server Components
| Topic | Description | Reference |
|---|---|---|
| cache | API for caching function results in Server Components | rsc-cache |
Advanced Components
| Topic | Description | Reference |
|---|---|---|
| Profiler | Component for measuring rendering performance | advanced-profiler |
| Activity | Component for hiding and restoring UI state | features-activity |
Testing
| Topic | Description | Reference |
|---|---|---|
| act | Test helper for applying pending updates before assertions | testing-act |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| Rules of Hooks | Fundamental rules for using React Hooks correctly | best-practices-rules-of-hooks |
| Component Purity | Rules for keeping React components and hooks pure | best-practices-purity |
Key Recommendations
- Use hooks at the top level - Never call hooks conditionally or in loops
- Keep components pure - Components should be idempotent and have no side effects during render
- Use useEffect for side effects - Synchronize with external systems using Effects
- Memoize expensive calculations - Use
useMemofor costly computations,useCallbackfor functions passed to memoized components - Code split with lazy - Use
lazyandSuspensefor route-based code splitting - Avoid premature optimization - Profile first, optimize only when needed
- Use React Compiler - Consider using React Compiler for automatic memoization
- Handle dependencies correctly - Always include all reactive values in Effect and memoization dependencies
Generation Info
- Source:
sources/react - Git SHA:
38b52cfdf059b2efc5ee3223a758efe00319fcc7 - Generated: 2026-01-31
Profiler
<Profiler> lets you measure rendering performance of a React tree programmatically. Use it to gather performance metrics in production.
Usage
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
console.log('Component:', id);
console.log('Phase:', phase);
console.log('Actual duration:', actualDuration);
console.log('Base duration:', baseDuration);
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<App />
</Profiler>
);
}Basic structure
<Profiler id="string" onRender={onRenderCallback}>
{children}
</Profiler>id: String identifying the profiled treeonRender: Callback called after each render
Key Points
- Production builds: Disabled by default, requires special build
- Performance overhead: Adds some overhead to rendering
- Programmatic: For programmatic performance measurement
- Multiple profilers: Can use multiple profilers in one app
Common Patterns
Measuring component performance
function onRender(id, phase, actualDuration, baseDuration) {
console.log(`${id} (${phase}):`, {
actual: actualDuration,
base: baseDuration,
difference: actualDuration - baseDuration
});
}
function App() {
return (
<Profiler id="App" onRender={onRender}>
<Header />
<Profiler id="Sidebar" onRender={onRender}>
<Sidebar />
</Profiler>
<MainContent />
</Profiler>
);
}Sending metrics to analytics
function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) {
analytics.track('render_performance', {
component: id,
phase,
duration: actualDuration,
baseDuration,
timestamp: commitTime
});
}Conditional profiling
function App() {
const shouldProfile = process.env.NODE_ENV === 'development';
if (shouldProfile) {
return (
<Profiler id="App" onRender={onRender}>
<AppContent />
</Profiler>
);
}
return <AppContent />;
}onRender Callback Parameters
id: Theidprop of the Profilerphase:"mount","update", or"nested-update"actualDuration: Time spent rendering (ms)baseDuration: Estimated time without optimizations (ms)startTime: When React began renderingcommitTime: When React committed the update
When to use Profiler
Use <Profiler> when:
- Measuring performance programmatically
- Sending metrics to analytics
- Debugging performance in production
- Comparing before/after optimizations
Use React DevTools Profiler when:
- Interactive profiling during development
- Visual performance analysis
- Component-level performance debugging
Best Practices
- Production builds: Enable profiling builds only when needed
- Minimal overhead: Use sparingly to avoid performance impact
- Aggregate data: Collect and analyze metrics over time
- Compare metrics: Compare
actualDurationvsbaseDuration
<!-- Source references:
- https://react.dev/reference/react/Profiler
-->
useDebugValue
useDebugValue is a React Hook that lets you add a label to a custom Hook in React DevTools. Improves debugging experience for custom hooks.
Usage
import { useDebugValue, useState } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}Basic structure
useDebugValue(value, format?);value: Value to display in DevToolsformat: Optional formatting function
Key Points
- DevTools only: Only affects React DevTools display
- Custom hooks: Use in custom hooks, not components
- Optional formatting: Can provide formatting function
- No production impact: Doesn't affect production builds
Common Patterns
Simple debug value
function useCounter(initialValue) {
const [count, setCount] = useState(initialValue);
useDebugValue(count);
return [count, setCount];
}Formatted debug value
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useDebugValue(
{ data, loading },
(value) => `${value.loading ? 'Loading' : 'Loaded'}: ${value.data?.length || 0} items`
);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(setData)
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
}Conditional formatting
function useAuth() {
const [user, setUser] = useState(null);
useDebugValue(
user,
(user) => user ? `Logged in as ${user.name}` : 'Not logged in'
);
return { user, setUser };
}When to use useDebugValue
Use useDebugValue when:
- Building custom hooks
- Hook state is complex
- Want better DevTools experience
- Debugging hook behavior
Don't use useDebugValue when:
- Hook is simple and self-explanatory
- Value is obvious from hook name
- Not building reusable hooks
Best Practices
- Custom hooks only: Use in custom hooks, not components
- Meaningful labels: Provide clear, descriptive labels
- Format complex values: Use formatter for complex objects
- Optional: Not required, but improves DX
<!-- Source references:
- https://react.dev/reference/react/useDebugValue
-->
useEffectEvent
useEffectEvent lets you extract non-reactive logic from Effects into reusable Effect Events. Helps avoid unnecessary Effect re-runs while accessing latest values.
Usage
import { useEffectEvent, useEffect } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', onConnected);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme not in deps, but always reads latest value
}Basic structure
const eventHandler = useEffectEvent(callback);callback: Function with non-reactive logic- Returns: Event handler function for use in Effects
Key Points
- Non-reactive: Doesn't cause Effect to re-run when values change
- Latest values: Always accesses latest props/state
- Effect only: Should only be called inside Effects
- Not dependency shortcut: Don't use to avoid dependencies
Common Patterns
Reading latest props without re-running
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onNavigate = useEffectEvent((visitedUrl) => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onNavigate(url);
}, [url]); // Effect runs when url changes, but always reads latest numberOfItems
}Separating events from effects
function ChatRoom({ roomId, theme }) {
const onMessage = useEffectEvent((message) => {
showNotification(message, theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('message', onMessage);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // Effect doesn't re-run when theme changes
}Avoiding stale closures
function Component({ userId }) {
const [count, setCount] = useState(0);
const onUpdate = useEffectEvent(() => {
// Always reads latest count, even if Effect doesn't re-run
console.log(`Count is ${count}`);
});
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
onUpdate();
}, 1000);
return () => clearInterval(interval);
}, []); // Effect runs once, but onUpdate always reads latest count
}When to use useEffectEvent
Use useEffectEvent when:
- Need to read latest values without re-running Effect
- Separating event handlers from Effect logic
- Avoiding stale closures in Effects
- Non-reactive logic in Effects
Don't use useEffectEvent when:
- Values should trigger Effect re-runs
- Trying to avoid dependencies (use proper deps instead)
- Logic is reactive and should be in dependency array
Best Practices
- Effect only: Only call inside Effects
- Latest values: Use when you need latest values without re-running
- Not for dependencies: Don't use to avoid dependency arrays
- Clear naming: Name Effect Events clearly (e.g.,
onConnected,onMessage)
<!-- Source references:
- https://react.dev/reference/react/useEffectEvent
-->
useImperativeHandle
useImperativeHandle lets you customize the ref handle exposed to parent components. Use it to expose specific methods instead of the entire DOM node.
Usage
import { useImperativeHandle, useRef } from 'react';
function MyInput({ ref }) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current?.focus();
},
scrollIntoView() {
inputRef.current?.scrollIntoView();
}
};
}, []);
return <input ref={inputRef} />;
}Basic structure
useImperativeHandle(ref, createHandle, dependencies?);ref: Ref received from parent (viaforwardRefor as prop in React 19+)createHandle: Function returning the handle to exposedependencies: Optional dependency array
Key Points
- Custom handle: Expose only specific methods, not entire DOM node
- Rarely needed: Most cases don't need this hook
- React 19:
refavailable as prop, noforwardRefneeded - Imperative: Breaks React's declarative paradigm
Common Patterns
Exposing specific methods
function MyInput({ ref }) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
inputRef.current.value = '';
inputRef.current.focus();
},
getValue: () => inputRef.current?.value
}), []);
return <input ref={inputRef} />;
}With forwardRef (React 18)
import { forwardRef, useImperativeHandle, useRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus()
}), []);
return <input ref={inputRef} />;
});Scrolling to element
function ScrollableList({ ref }) {
const listRef = useRef(null);
useImperativeHandle(ref, () => ({
scrollToItem: (index) => {
const item = listRef.current.children[index];
item?.scrollIntoView({ behavior: 'smooth' });
}
}), []);
return <div ref={listRef}>{/* items */}</div>;
}When to use useImperativeHandle
Use useImperativeHandle when:
- Parent needs to call specific methods on child
- You want to hide internal implementation
- Building reusable component libraries
Prefer regular refs when:
- Parent just needs DOM node access
- Standard DOM methods are sufficient
- Following React's declarative patterns
Best Practices
- Minimal API: Expose only necessary methods
- Document behavior: Clearly document exposed methods
- Avoid overuse: Most cases don't need this hook
- TypeScript: Use TypeScript for type-safe ref handles
<!-- Source references:
- https://react.dev/reference/react/useImperativeHandle
-->
useInsertionEffect
useInsertionEffect allows inserting elements into the DOM before any layout Effects fire. Primarily for CSS-in-JS library authors.
Usage
import { useInsertionEffect } from 'react';
// Inside your CSS-in-JS library
function useCSS(rule) {
useInsertionEffect(() => {
// Inject <style> tags here
if (!isInserted.has(rule)) {
isInserted.add(rule);
document.head.appendChild(createStyleElement(rule));
}
});
return rule;
}Basic structure
useInsertionEffect(setup, dependencies?);Same API as useEffect, but runs before layout Effects.
Key Points
- For library authors: Primarily for CSS-in-JS libraries
- Runs before layout: Executes before
useLayoutEffect - No state updates: Can't update state from inside
- Refs not attached: Refs may not be attached when it runs
Common Patterns
CSS-in-JS style injection
let isInserted = new Set();
function useCSS(rule) {
useInsertionEffect(() => {
if (!isInserted.has(rule)) {
isInserted.add(rule);
const style = document.createElement('style');
style.textContent = rule;
document.head.appendChild(style);
}
});
return rule;
}Cleanup styles
function useCSS(rule) {
useInsertionEffect(() => {
const style = document.createElement('style');
style.textContent = rule;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, [rule]);
}When to use useInsertionEffect
Use useInsertionEffect when:
- Building a CSS-in-JS library
- Need to inject styles before layout calculations
- Need to ensure styles are available before
useLayoutEffect
Prefer useEffect or useLayoutEffect for:
- Regular component side effects
- DOM measurements
- Most other use cases
Performance Considerations
- Runtime injection: Not recommended for performance
- Static extraction: Prefer CSS files for static styles
- Inline styles: Use inline styles for dynamic styles
<!-- Source references:
- https://react.dev/reference/react/useInsertionEffect
-->
useSyncExternalStore
useSyncExternalStore is a React Hook that lets you subscribe to an external store. Use it to integrate with third-party state management libraries or browser APIs.
Usage
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(
todosStore.subscribe,
todosStore.getSnapshot
);
return <div>{todos.map(todo => <Todo key={todo.id} {...todo} />)}</div>;
}Basic structure
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);subscribe: Function that subscribes to store changesgetSnapshot: Function that returns current store snapshotgetServerSnapshot: Optional function for server rendering
Key Points
- External stores: Integrates with non-React state management
- Immutable snapshots:
getSnapshotmust return immutable values - Server rendering: Requires
getServerSnapshotfor SSR - Synchronous: Store mutations trigger synchronous updates
Common Patterns
Subscribing to external store
function createStore(initialState) {
let state = initialState;
let listeners = new Set();
return {
getSnapshot: () => state,
subscribe: (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
},
setState: (newState) => {
state = newState;
listeners.forEach(listener => listener());
}
};
}
const store = createStore({ count: 0 });
function Component() {
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
return <div>{state.count}</div>;
}Browser API integration
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine
);
}With server snapshot
function Component() {
const data = useSyncExternalStore(
store.subscribe,
store.getSnapshot,
() => store.getServerSnapshot() // For SSR
);
return <div>{data}</div>;
}Best Practices
- Immutable snapshots: Always return new objects/arrays when data changes
- Stable subscribe: Declare
subscribeoutside component to avoid re-subscribing - Server snapshot: Provide
getServerSnapshotfor SSR compatibility - Don't suspend: Avoid suspending based on store values
<!-- Source references:
- https://react.dev/reference/react/useSyncExternalStore
-->
Component Purity
Components and Hooks must be pure to work correctly with React's rendering model. Purity means components are idempotent and have no side effects during render.
What is purity?
A pure component or hook:
- Idempotent: Always returns the same output for the same inputs
- No side effects in render: Side effects run in event handlers or Effects
- No mutation of non-local values: Only mutate locally created values
Idempotency
Components must return the same output for the same props, state, and context.
❌ Not idempotent
function Clock() {
const time = new Date(); // ❌ Different result every render
return <span>{time.toLocaleString()}</span>;
}
function Random() {
const value = Math.random(); // ❌ Different result every render
return <span>{value}</span>;
}✅ Idempotent
function Clock({ time }) {
return <span>{time.toLocaleString()}</span>;
}
function useTime() {
const [time, setTime] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(id);
}, []);
return time;
}Side effects in render
Side effects must not run during render. Move them to event handlers or Effects.
❌ Side effect in render
function Component({ product }) {
document.title = product.title; // ❌ Side effect in render
return <div>{product.name}</div>;
}✅ Side effect in Effect
function Component({ product }) {
useEffect(() => {
document.title = product.title; // ✅ Side effect in Effect
}, [product.title]);
return <div>{product.name}</div>;
}Mutation
✅ Local mutation is fine
function FriendList({ friends }) {
const items = []; // ✅ Locally created
for (let friend of friends) {
items.push(<Friend key={friend.id} friend={friend} />); // ✅ Local mutation
}
return <section>{items}</section>;
}❌ Non-local mutation
const items = []; // ❌ Created outside component
function FriendList({ friends }) {
for (let friend of friends) {
items.push(<Friend key={friend.id} friend={friend} />); // ❌ Mutates non-local
}
return <section>{items}</section>;
}Props and state immutability
Never mutate props or state directly.
❌ Mutating props
function Component({ item }) {
item.url = new URL(item.url, base); // ❌ Mutating props
return <Link url={item.url}>{item.title}</Link>;
}✅ Creating new values
function Component({ item }) {
const url = new URL(item.url, base); // ✅ New value
return <Link url={url}>{item.title}</Link>;
}❌ Mutating state
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
count = count + 1; // ❌ Mutating state directly
}
}✅ Using setter
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1); // ✅ Using setter
}
}Render phase vs commit phase
- Render: Calculating what UI should look like (must be pure)
- Commit: Applying changes to DOM (side effects allowed)
Code at the top level of a component runs during render. Event handlers and Effects run outside render.
function Component() {
// This runs during render (must be pure)
const value = computeValue();
// This runs outside render (side effects OK)
useEffect(() => {
// Side effect
}, []);
const handleClick = () => {
// This runs outside render (side effects OK)
};
return <div onClick={handleClick}>{value}</div>;
}Benefits of purity
- Predictable: Same inputs = same output
- Optimizable: React can skip unnecessary renders
- Debuggable: Easier to reason about component behavior
- Testable: Pure functions are easier to test
<!-- Source references:
- https://react.dev/reference/rules/components-and-hooks-must-be-pure
-->
Rules of Hooks
Hooks must follow specific rules to work correctly. Breaking these rules leads to bugs and unpredictable behavior.
Rule 1: Only call Hooks at the top level
Don't call Hooks inside loops, conditions, nested functions, or try/catch blocks.
✅ Correct usage
function Component() {
// ✅ Good: top-level in function component
const [count, setCount] = useState(0);
const theme = useContext(ThemeContext);
// ✅ Good: conditional logic after hooks
if (count > 10) {
return <div>Too many</div>;
}
return <div>{count}</div>;
}
function useCustomHook() {
// ✅ Good: top-level in custom hook
const [value, setValue] = useState(null);
return value;
}❌ Incorrect usage
// ❌ Bad: inside condition
function Bad({ cond }) {
if (cond) {
const [count, setCount] = useState(0);
}
}
// ❌ Bad: inside loop
function Bad() {
for (let i = 0; i < 10; i++) {
const [count, setCount] = useState(0);
}
}
// ❌ Bad: after conditional return
function Bad({ cond }) {
if (cond) {
return null;
}
const [count, setCount] = useState(0);
}
// ❌ Bad: inside event handler
function Bad() {
function handleClick() {
const [count, setCount] = useState(0);
}
}
// ❌ Bad: inside try/catch
function Bad() {
try {
const [count, setCount] = useState(0);
} catch {
const [count, setCount] = useState(1);
}
}Rule 2: Only call Hooks from React functions
Only call Hooks from React function components or custom Hooks.
✅ Correct usage
// ✅ Good: React function component
function Component() {
const value = useCustomHook();
}
// ✅ Good: custom Hook
function useCustomHook() {
const [value, setValue] = useState(0);
return value;
}❌ Incorrect usage
// ❌ Bad: regular JavaScript function
function regularFunction() {
const [value, setValue] = useState(0);
}
// ❌ Bad: class component
class Bad extends React.Component {
render() {
const [value, setValue] = useState(0); // ❌
}
}Why these rules exist
React relies on the order of Hook calls to track state between renders. If Hooks are called conditionally or in loops, the order changes between renders, causing bugs.
ESLint plugin
Use eslint-plugin-react-hooks to catch violations:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}Common mistakes and fixes
Conditional hook call
// ❌ Bad
function Component({ show }) {
if (show) {
const [value, setValue] = useState(0);
}
}
// ✅ Good: call hook unconditionally
function Component({ show }) {
const [value, setValue] = useState(0);
if (!show) return null;
// use value...
}Hook in callback
// ❌ Bad
function Component() {
const handleClick = () => {
const [value, setValue] = useState(0);
};
}
// ✅ Good: move hook to top level
function Component() {
const [value, setValue] = useState(0);
const handleClick = () => {
setValue(v => v + 1);
};
}<!-- Source references:
- https://react.dev/reference/rules/rules-of-hooks
-->
createContext
createContext lets you create a context that components can provide or read. It's used with useContext to avoid prop drilling.
Usage
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}
function App() {
return (
<ThemeContext value="dark">
<Button />
</ThemeContext>
);
}Basic structure
const SomeContext = createContext(defaultValue);defaultValue: Value used when no Provider exists above component- Returns: Context object with
ProviderandConsumerproperties
Key Points
- Default value: Used when no Provider exists (static, never changes)
- Context object: Doesn't hold information, represents which context to read
- React 19: Can use
<SomeContext>directly as Provider - Legacy: Use
<SomeContext.Provider>in older React versions
Common Patterns
Creating context
const ThemeContext = createContext(null);
const UserContext = createContext(null);
const LocaleContext = createContext('en');Providing context (React 19+)
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext value={theme}>
<Page />
</ThemeContext>
);
}Providing context (Legacy)
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Page />
</ThemeContext.Provider>
);
}Custom Provider component
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext value={{ theme, setTheme }}>
{children}
</ThemeContext>
);
}
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}Multiple contexts
function App() {
return (
<ThemeContext value="dark">
<UserContext value={user}>
<LocaleContext value="en">
<Page />
</LocaleContext>
</UserContext>
</ThemeContext>
);
}Best Practices
- Split contexts: Separate contexts for unrelated values to avoid unnecessary re-renders
- Default value: Use
nullif no meaningful default exists - Custom hooks: Create custom hooks for context access with error handling
- TypeScript: Use TypeScript for type-safe context values
<!-- Source references:
- https://react.dev/reference/react/createContext
-->
Fragment
<Fragment> (or <>...</>) lets you group multiple elements without adding an extra DOM node. Useful when you need to return multiple elements from a component.
Usage
import { Fragment } from 'react';
function Post() {
return (
<Fragment>
<PostTitle />
<PostBody />
</Fragment>
);
}
// Or use shorthand syntax
function Post() {
return (
<>
<PostTitle />
<PostBody />
</>
);
}Basic structure
<Fragment key={key}>
{children}
</Fragment>
// Shorthand
<>
{children}
</>Key Points
- No DOM node: Fragments don't create wrapper elements
- Key prop: Use
<Fragment>syntax when you need keys - Grouping: Useful for returning multiple elements
- Refs: Can accept refs (React 19+)
Common Patterns
Returning multiple elements
function Component() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}Fragments with keys
function List({ items }) {
return items.map(item => (
<Fragment key={item.id}>
<ItemTitle item={item} />
<ItemDescription item={item} />
</Fragment>
));
}Conditional rendering
function Component({ show }) {
return (
<>
{show && <Modal />}
<Content />
</>
);
}Avoiding wrapper divs
// ❌ Bad: Adds unnecessary div
function Component() {
return (
<div>
<Child1 />
<Child2 />
</div>
);
}
// ✅ Good: No wrapper element
function Component() {
return (
<>
<Child1 />
<Child2 />
</>
);
}When to use Fragment
Use Fragment when:
- Returning multiple elements from a component
- Grouping elements in lists without wrappers
- Avoiding unnecessary DOM nodes
Don't use Fragment when:
- You need a wrapper element for styling
- You need to attach event handlers to a container
- A single element is sufficient
FragmentInstance (React 19+)
When using refs with fragments, React provides a FragmentInstance with methods for:
- Event handling:
addEventListener,removeEventListener,dispatchEvent - Layout:
getClientRects,getRootNode,compareDocumentPosition - Focus:
focus,focusLast,blur - Observers:
observeUsing,unobserveUsing
<!-- Source references:
- https://react.dev/reference/react/Fragment
-->
memo
memo lets you skip re-rendering a component when its props are unchanged. It's a performance optimization that compares props using shallow equality.
Usage
import { memo } from 'react';
const Greeting = memo(function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
});Basic structure
const MemoizedComponent = memo(Component, arePropsEqual?);Component: Component to memoizearePropsEqual: Optional custom comparison function
Key Points
- Shallow comparison: By default, compares props using
Object.is - Performance optimization: Not a guarantee - React may still re-render
- State/context changes: Component still re-renders if its own state or context changes
- React Compiler: Consider using React Compiler for automatic memoization
Common Patterns
Basic memoization
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
// Expensive rendering logic
return <div>{/* complex UI */}</div>;
});With useCallback
const Child = memo(function Child({ onClick }) {
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
// ...
}, []);
return <Child onClick={handleClick} />;
}Custom comparison
const Component = memo(
function Component({ items }) {
return <div>{items.map(item => <Item key={item.id} {...item} />)}</div>;
},
(prevProps, nextProps) => {
// Custom comparison logic
return prevProps.items.length === nextProps.items.length;
}
);With TypeScript
interface Props {
name: string;
age: number;
}
const Component = memo(function Component({ name, age }: Props) {
return <div>{name} is {age}</div>;
});When to use memo
Use memo when:
- Component renders frequently with same props
- Component is expensive to render
- Parent re-renders often but props don't change
Don't use memo when:
- Component always receives new props
- Component is cheap to render
- Premature optimization without profiling
Limitations
- Shallow comparison: Only compares props, not nested objects
- Function props: New function references cause re-renders (use
useCallback) - Object props: New object references cause re-renders (use
useMemo)
<!-- Source references:
- https://react.dev/reference/react/memo
-->
StrictMode
<StrictMode> enables additional development-only checks and warnings for your components. It helps find bugs early during development.
Usage
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);Basic structure
<StrictMode>
{children}
</StrictMode>No props accepted. Wraps component tree to enable strict checks.
Key Points
- Development only: Checks only run in development
- No production impact: Doesn't affect production builds
- Double rendering: Components render twice to detect impurities
- Effect cleanup: Effects run twice to find missing cleanup
What StrictMode Checks
Double rendering
Components render twice to detect impure rendering:
let renderCount = 0;
function Component() {
renderCount++; // Will be 2 in StrictMode
return <div>Rendered {renderCount} times</div>;
}Effect cleanup
Effects run twice to ensure cleanup is implemented:
useEffect(() => {
const connection = createConnection();
// StrictMode calls this twice
// Must return cleanup function
return () => connection.disconnect();
}, []);Ref callbacks
Ref callbacks run twice to ensure cleanup:
<div ref={(node) => {
// Called twice in StrictMode
if (node) {
// Setup
} else {
// Cleanup
}
}} />Deprecated APIs
Warns about usage of deprecated APIs.
Common Patterns
Wrapping entire app
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);Wrapping part of app
function App() {
return (
<>
<Header />
<StrictMode>
<DebugPanel />
</StrictMode>
<Footer />
</>
);
}Best Practices
- Enable for new apps: Always use StrictMode in new projects
- Fix warnings: Address all StrictMode warnings
- Pure components: Ensure components are pure (idempotent)
- Cleanup effects: Always return cleanup functions from Effects
What StrictMode Doesn't Do
- No production checks: Only runs in development
- No performance impact: Doesn't affect production performance
- No guarantees: Doesn't guarantee code correctness
<!-- Source references:
- https://react.dev/reference/react/StrictMode
-->
Suspense
<Suspense> lets you display a fallback UI until its children have finished loading. It's used for code splitting, data fetching, and other asynchronous operations.
Usage
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>
);
}Basic structure
<Suspense fallback={fallback}>
{children}
</Suspense>fallback: UI to show while children are loadingchildren: Components that may suspend
Key Points
- Automatic fallback: Shows fallback when children suspend
- Nested boundaries: Can have multiple Suspense boundaries
- State preservation: React doesn't preserve state for suspended renders before first mount
- Layout Effects: Cleans up layout effects when hiding suspended content
Common Patterns
Code splitting with lazy
import { lazy, Suspense } from 'react';
const Albums = lazy(() => import('./Albums'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>
);
}Data fetching
function ArtistPage({ artist }) {
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<AlbumsGlimmer />}>
<Albums artistId={artist.id} />
</Suspense>
<Suspense fallback={<BiographyGlimmer />}>
<Biography artistId={artist.id} />
</Suspense>
</>
);
}Revealing content together
function ProfilePage() {
return (
<Suspense fallback={<BigSpinner />}>
<ProfileDetails />
<Suspense fallback={<PostsGlimmer />}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}Nested Suspense boundaries
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<NavBar />
<Suspense fallback={<SidebarGlimmer />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentGlimmer />}>
<Content />
</Suspense>
</Suspense>
);
}What causes Suspense?
Components suspend when they:
- Use
lazy()for code splitting - Use data fetching libraries that support Suspense
- Use
use()hook with a Promise - Throw a Promise during render (in data fetching libraries)
Best Practices
- Lightweight fallbacks: Keep fallback components simple
- Multiple boundaries: Use separate boundaries for independent content
- Revealing together: Wrap related content in same boundary to reveal together
- Error boundaries: Combine with Error Boundaries for error handling
<!-- Source references:
- https://react.dev/reference/react/Suspense
-->
useContext
useContext is a React Hook that lets you read and subscribe to context from your component. It's used with createContext to avoid prop drilling.
Usage
import { useContext, createContext } from 'react';
const ThemeContext = createContext('light');
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Button />
</ThemeContext.Provider>
);
}Creating context
import { createContext } from 'react';
const ThemeContext = createContext('light'); // default valueProviding context
Wrap components with the Provider:
<ThemeContext.Provider value="dark">
<ChildComponent />
</ThemeContext.Provider>Reading context
function Component() {
const theme = useContext(ThemeContext);
// theme is 'dark' if Provider exists, otherwise 'light'
}Key Points
useContextlooks for the closest Provider above the component- If no Provider exists, returns the
defaultValuefromcreateContext - Context value is always up-to-date - React re-renders consumers when context changes
useContextcall is not affected by Providers returned from the same component- Context comparison uses
Object.is- changing object reference triggers re-render
Common Patterns
Theme context
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}User context
const UserContext = createContext(null);
function UserProvider({ children }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, []);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}Multiple contexts
function Component() {
const theme = useContext(ThemeContext);
const user = useContext(UserContext);
const locale = useContext(LocaleContext);
// ...
}Custom hook wrapper
function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}Performance Considerations
- Context changes trigger re-renders of all consumers
- Use
memoto prevent unnecessary re-renders, but context still updates - Split contexts to avoid unnecessary re-renders:
// ❌ Bad: theme change re-renders user consumers
const AppContext = createContext({ theme, user });
// ✅ Good: separate contexts
const ThemeContext = createContext(null);
const UserContext = createContext(null);<!-- Source references:
- https://react.dev/reference/react/useContext
- https://react.dev/reference/react/createContext
-->
useEffect
useEffect is a React Hook that lets you synchronize a component with an external system. Use it for side effects like data fetching, subscriptions, or manually changing the DOM.
Usage
import { useEffect } from 'react';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
}Basic structure
useEffect(setup, dependencies?)setup: Function with your effect's logic, optionally returns a cleanup functiondependencies: Array of reactive values (props, state, variables) used inside the effect
Dependencies
- With dependencies: Effect runs when dependencies change
- Empty array `[]`: Effect runs only once after mount
- No dependencies: Effect runs after every render
// Runs when roomId changes
useEffect(() => {
// ...
}, [roomId]);
// Runs only once after mount
useEffect(() => {
// ...
}, []);
// Runs after every render (usually avoid this)
useEffect(() => {
// ...
});Cleanup function
Return a cleanup function to clean up subscriptions, timers, or other resources:
useEffect(() => {
const intervalId = setInterval(() => {
// ...
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);Key Points
- Effects run after the browser paints the screen (use
useLayoutEffectif you need to measure layout) - Effects only run on the client - they don't run during server rendering
- In Strict Mode, React runs setup+cleanup twice in development to detect missing cleanup
- Effects are an escape hatch - if you're not synchronizing with an external system, you might not need an Effect
Common Patterns
Fetching data
useEffect(() => {
let cancelled = false;
async function fetchData() {
const response = await fetch(`/api/user/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
}
}
fetchData();
return () => {
cancelled = true;
};
}, [userId]);Subscribing to events
useEffect(() => {
function handleScroll() {
// ...
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);Updating document title
useEffect(() => {
document.title = `${count} items`;
}, [count]);Controlling non-React widgets
useEffect(() => {
const map = new MapWidget(containerRef.current);
map.setZoomLevel(zoomLevel);
return () => {
map.destroy();
};
}, [zoomLevel]);Avoiding Common Mistakes
Missing dependencies
// ❌ Bad: missing 'userId' dependency
useEffect(() => {
fetchUser(userId);
}, []);
// ✅ Good: includes all dependencies
useEffect(() => {
fetchUser(userId);
}, [userId]);Infinite loops
// ❌ Bad: creates new object every render
useEffect(() => {
setOptions({ ...options, color: 'red' });
}, [options]);
// ✅ Good: use updater function
useEffect(() => {
setOptions(prev => ({ ...prev, color: 'red' }));
}, []);Reading latest props/state
// If you need the latest value without re-running effect
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // Uses updater function
}, 1000);
return () => clearInterval(id);
}, []); // Empty deps - effect doesn't re-run<!-- Source references:
- https://react.dev/reference/react/useEffect
-->
useReducer
useReducer is a React Hook that lets you add a reducer to your component. It's useful for managing complex state logic that involves multiple sub-values or when the next state depends on the previous one.
Usage
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<button onClick={() => dispatch({ type: 'increment' })}>
+
</button>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'decrement' })}>
-
</button>
</>
);
}Basic structure
const [state, dispatch] = useReducer(reducer, initialArg, init?);reducer: Function that takes(state, action)and returns the next stateinitialArg: Initial state valueinit: Optional initializer function(initialArg) => initialState
Reducer function
The reducer must be pure and return the next state:
function reducer(state, action) {
// Calculate and return next state
return nextState;
}Dispatch function
Call dispatch with an action object to update state:
dispatch({ type: 'increment', payload: 5 });Key Points
- Predictable updates: All state updates go through the reducer
- Complex state: Better than
useStatewhen state has multiple sub-values - Stable dispatch:
dispatchfunction has stable identity (safe to omit from dependencies) - Batching: React batches multiple dispatches in event handlers
Common Patterns
Simple counter
function reducer(state, action) {
return { count: state.count + action.payload };
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<button onClick={() => dispatch({ type: 'add', payload: 1 })}>
{state.count}
</button>
);
}Form state
function formReducer(state, action) {
switch (action.type) {
case 'change':
return { ...state, [action.field]: action.value };
case 'reset':
return { name: '', email: '' };
default:
return state;
}
}
function Form() {
const [state, dispatch] = useReducer(formReducer, { name: '', email: '' });
return (
<form>
<input
value={state.name}
onChange={(e) => dispatch({
type: 'change',
field: 'name',
value: e.target.value
})}
/>
</form>
);
}Lazy initialization
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'reset':
return init(action.payload);
default:
return state;
}
}
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
// ...
}With TypeScript
type State = { count: number };
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: action.payload };
default:
return state;
}
}When to use useReducer vs useState
Use useReducer when:
- State has multiple sub-values
- Next state depends on previous state
- Complex state update logic
- You want predictable state updates
Use useState when:
- Simple state values
- Independent state updates
- Straightforward update logic
<!-- Source references:
- https://react.dev/reference/react/useReducer
-->
useRef
useRef is a React Hook that lets you reference a value that's not needed for rendering. Unlike state, changing a ref doesn't trigger a re-render.
Usage
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(null);
function handleStart() {
intervalRef.current = setInterval(() => {
// ...
}, 1000);
}
function handleStop() {
clearInterval(intervalRef.current);
}
// ...
}Basic structure
const ref = useRef(initialValue);Returns an object with a current property:
- Initially set to
initialValue - Can be mutated without causing re-renders
- Persists between renders
Key Points
- Mutable: You can mutate
ref.currentdirectly - No re-renders: Changing
ref.currentdoesn't trigger re-renders - Persistent: Ref object persists across renders
- Don't read/write during render: Except for initialization
Common Use Cases
Storing timeout/interval IDs
function Timer() {
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
// ...
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
}Storing previous values
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}Avoiding recreating objects
function Component() {
// ❌ Bad: new object every render
const options = { enabled: true };
// ✅ Good: same object every render
const optionsRef = useRef({ enabled: true });
const options = optionsRef.current;
}DOM references
function Input() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current?.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleFocus}>Focus</button>
</>
);
}Storing mutable values
function Component() {
const countRef = useRef(0);
function handleClick() {
countRef.current += 1;
console.log(countRef.current); // No re-render
}
return <button onClick={handleClick}>Click</button>;
}Differences from useState
| Feature | useRef | useState |
|---|---|---|
| Triggers re-render | ❌ No | ✅ Yes |
| Mutable | ✅ Yes | ❌ No (use setter) |
| Initial value | Direct | Direct or function |
| Returns | { current: value } | [value, setter] |
Rules
- Don't read/write `ref.current` during render (except initialization)
- Use refs for values that don't affect rendering
- Use state for values that should trigger re-renders
<!-- Source references:
- https://react.dev/reference/react/useRef
-->
useState
useState is a React Hook that lets you add state to your component. It returns a state variable and a setter function to update it.
Usage
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}Initial state
You can pass an initial value directly or use a function for lazy initialization:
// Direct value
const [name, setName] = useState('Taylor');
// Lazy initialization (function runs only once)
const [todos, setTodos] = useState(() => createTodos());Updating state
Use the setter function to update state. You can pass a new value or an updater function:
// Direct value
setCount(count + 1);
// Updater function (recommended when using previous state)
setCount(prevCount => prevCount + 1);Multiple state variables
Declare multiple state variables separately:
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
// ...
}Key Points
- State updates are batched - React batches multiple state updates in event handlers
- State updates are asynchronous - reading state immediately after
setStatereturns the old value - State is immutable - never mutate state directly, always use the setter function
- State is local - each component instance has its own state
- In Strict Mode, React calls initializer/updater functions twice in development to detect impurities
Common Patterns
Updating objects in state
const [person, setPerson] = useState({ name: 'Taylor', age: 28 });
// Create a new object
setPerson({ ...person, age: 29 });
// Or use updater function
setPerson(prev => ({ ...prev, age: 29 }));Updating arrays in state
const [items, setItems] = useState(['apple', 'banana']);
// Add item
setItems([...items, 'orange']);
// Remove item
setItems(items.filter(item => item !== 'banana'));
// Update item
setItems(items.map(item =>
item === 'apple' ? 'pear' : item
));Avoiding recreating initial state
Use a function for expensive initializations:
// ❌ Bad: runs on every render
const [todos, setTodos] = useState(createTodos());
// ✅ Good: runs only once
const [todos, setTodos] = useState(() => createTodos());<!-- Source references:
- https://react.dev/reference/react/useState
-->
Activity
<Activity> lets you hide and restore the UI and internal state of its children without unmounting them. Useful for preserving component state when temporarily hiding content.
Usage
import { Activity } from 'react';
function App() {
const [showSidebar, setShowSidebar] = useState(true);
return (
<>
<button onClick={() => setShowSidebar(!showSidebar)}>
Toggle Sidebar
</button>
<Activity mode={showSidebar ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>
</>
);
}Basic structure
<Activity mode="visible" | "hidden">
{children}
</Activity>mode:'visible'or'hidden'children: Components to show/hide
Key Points
- Preserves state: Component state is preserved when hidden
- Destroys Effects: Effects are cleaned up when hidden, recreated when visible
- Lower priority: Hidden components render at lower priority
- CSS display: Uses
display: noneto hide
Common Patterns
Preserving sidebar state
function App() {
const [showSidebar, setShowSidebar] = useState(true);
return (
<>
<button onClick={() => setShowSidebar(!showSidebar)}>
{showSidebar ? 'Hide' : 'Show'} Sidebar
</button>
<Activity mode={showSidebar ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>
</>
);
}Tab content with preserved state
function TabContainer() {
const [activeTab, setActiveTab] = useState('tab1');
return (
<>
<Tabs activeTab={activeTab} onChange={setActiveTab} />
<Activity mode={activeTab === 'tab1' ? 'visible' : 'hidden'}>
<Tab1Content />
</Activity>
<Activity mode={activeTab === 'tab2' ? 'visible' : 'hidden'}>
<Tab2Content />
</Activity>
</>
);
}Differences from conditional rendering
// ❌ Unmounts component, loses state
{showSidebar && <Sidebar />}
// ✅ Preserves state when hidden
<Activity mode={showSidebar ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>When to use Activity
Use <Activity> when:
- Need to preserve component state when hiding
- Temporarily hiding content that will be shown again
- Want to clean up Effects when hidden
- Building tab interfaces with preserved state
Use conditional rendering when:
- Component won't be shown again
- Don't need to preserve state
- Want to completely remove from DOM
Best Practices
- State preservation: Use when state preservation is important
- Effect cleanup: Effects are cleaned up automatically
- Performance: Hidden components render at lower priority
- ViewTransition: Works with ViewTransition for animations
<!-- Source references:
- https://react.dev/reference/react/Activity
-->
lazy
lazy lets you defer loading a component's code until it's rendered for the first time. Use it with Suspense for code splitting.
Usage
import { lazy, Suspense } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview'));
function App() {
return (
<Suspense fallback={<Loading />}>
<MarkdownPreview />
</Suspense>
);
}Basic structure
const LazyComponent = lazy(load);load: Function that returns a Promise resolving to a component- Component must be exported as
defaultexport
Key Points
- Code splitting: Reduces initial bundle size
- Suspense required: Must wrap lazy components in
<Suspense> - Default export: Lazy-loaded component must be default export
- Caching: React caches the loaded component
Common Patterns
Basic lazy loading
const Albums = lazy(() => import('./Albums'));
const Biography = lazy(() => import('./Biography'));
function ArtistPage({ artist }) {
return (
<>
<Suspense fallback={<AlbumsGlimmer />}>
<Albums artistId={artist.id} />
</Suspense>
<Suspense fallback={<BiographyGlimmer />}>
<Biography artistId={artist.id} />
</Suspense>
</>
);
}Conditional lazy loading
const MarkdownPreview = lazy(() => import('./MarkdownPreview'));
function MarkdownEditor() {
const [showPreview, setShowPreview] = useState(false);
return (
<>
<button onClick={() => setShowPreview(!showPreview)}>
Toggle Preview
</button>
{showPreview && (
<Suspense fallback={<Loading />}>
<MarkdownPreview />
</Suspense>
)}
</>
);
}Named exports workaround
// If component is named export
const MarkdownPreview = lazy(() =>
import('./MarkdownPreview').then(module => ({
default: module.MarkdownPreview
}))
);With error boundaries
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
}Best Practices
- Route-based splitting: Lazy load route components
- Heavy components: Lazy load components with large dependencies
- User-triggered: Lazy load features triggered by user actions
- Error handling: Combine with Error Boundaries
Limitations
- Default export required: Component must be default export (or use workaround)
- Suspense required: Must wrap in
<Suspense>boundary - No SSR: Lazy components don't work with server-side rendering
<!-- Source references:
- https://react.dev/reference/react/lazy
-->
startTransition
startTransition lets you mark state updates as non-blocking transitions. Similar to useTransition, but can be used outside components.
Usage
import { startTransition } from 'react';
function TabContainer() {
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return <TabButton onClick={() => selectTab('about')}>About</TabButton>;
}Basic structure
startTransition(action);action: Function that updates state- Marks all state updates inside as transitions
Key Points
- Non-blocking: Keeps UI responsive during updates
- No pending flag: Doesn't provide
isPendinglikeuseTransition - Works outside components: Can be called from data libraries
- Interruptible: New updates interrupt ongoing transitions
Common Patterns
Marking updates as transitions
import { startTransition } from 'react';
function updateTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}In data libraries
// In a data fetching library
function fetchData(query) {
startTransition(() => {
setData(computeData(query));
});
}With async operations
async function handleSubmit() {
startTransition(async () => {
const result = await submitForm();
// Need to wrap in another startTransition
startTransition(() => {
setResult(result);
});
});
}Differences from useTransition
| Feature | startTransition | useTransition |
|---|---|---|
| Pending flag | ❌ No | ✅ Yes (isPending) |
| Use outside components | ✅ Yes | ❌ No |
| Hook | ❌ No | ✅ Yes |
| Use case | Data libraries | Components |
When to use startTransition
Use startTransition when:
- You need to mark updates from outside components
- Building data libraries that update state
- You don't need pending indicators
Use useTransition when:
- You need
isPendingflag - Working inside components
- You want to show loading states
Best Practices
- Wrap state updates: Wrap all state updates that should be non-blocking
- Don't use for text inputs: Can't control text inputs with transitions
- Handle async: Wrap state updates after
awaitin anotherstartTransition
<!-- Source references:
- https://react.dev/reference/react/startTransition
-->
use
use is a React API that lets you read the value of a resource like a Promise or context. Unlike hooks, it can be called conditionally and in loops.
Usage
import { use } from 'react';
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
return <div>{message.text}</div>;
}
function Button() {
const theme = use(ThemeContext);
return <button className={theme}>Click</button>;
}Basic structure
const value = use(resource);resource: A Promise or Context object- Returns: Resolved value from Promise or context value
Key Points
- Can be conditional: Unlike hooks, can be called in
ifstatements and loops - Suspense integration: Works with Suspense boundaries for Promises
- Error boundaries: Promise rejections trigger error boundaries
- Must be in component: Still must be called inside components or hooks
Common Patterns
Reading Promises
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
return <div>{message.text}</div>;
}
function App() {
const messagePromise = fetch('/api/message').then(r => r.json());
return (
<Suspense fallback={<Loading />}>
<MessageComponent messagePromise={messagePromise} />
</Suspense>
);
}Conditional context reading
function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return null;
}Reading context in loops
function Component({ items }) {
return items.map(item => {
const theme = use(ThemeContext);
return <Item key={item.id} item={item} theme={theme} />;
});
}Streaming data from server
// Server Component
async function ServerComponent() {
const dataPromise = fetchData();
return <ClientComponent dataPromise={dataPromise} />;
}
// Client Component
'use client';
function ClientComponent({ dataPromise }) {
const data = use(dataPromise);
return <div>{data.content}</div>;
}Differences from useContext
usecan be called conditionally and in loopsuseContextmust be called at top leveluseis more flexible for dynamic context access
Differences from async/await
useworks with Suspense boundariesasync/awaitin Server Components doesn't suspendusere-renders after Promise resolvesasync/awaitcontinues rendering from await point
Best Practices
- Server Components: Prefer
async/awaitin Server Components - Client Components: Use
usefor Promises in Client Components - Stable Promises: Pass Promises from Server Components for stability
- Error handling: Combine with Error Boundaries
<!-- Source references:
- https://react.dev/reference/react/use
-->
useActionState
useActionState is a Hook that allows you to update state based on the result of a form action. Useful for form handling with server actions.
Usage
import { useActionState } from 'react';
async function increment(previousState, formData) {
return previousState + 1;
}
function StatefulForm() {
const [state, formAction, isPending] = useActionState(increment, 0);
return (
<form>
<p>Count: {state}</p>
<button formAction={formAction}>Increment</button>
{isPending && <span>Submitting...</span>}
</form>
);
}Basic structure
const [state, formAction, isPending] = useActionState(action, initialState, permalink?);action: Function called when form is submittedinitialState: Initial state valuepermalink: Optional URL for progressive enhancement- Returns:
[state, formAction, isPending]
Key Points
- Form actions: Designed for form submission handling
- Server functions: Works with React Server Components
- Progressive enhancement: Supports forms before JavaScript loads
- Pending state: Provides
isPendingflag for loading states
Common Patterns
Basic form action
async function submitForm(previousState, formData) {
const name = formData.get('name');
// Process form...
return { success: true, message: 'Form submitted' };
}
function MyForm() {
const [state, formAction] = useActionState(submitForm, null);
return (
<form action={formAction}>
<input name="name" />
<button type="submit">Submit</button>
{state?.message && <p>{state.message}</p>}
</form>
);
}With error handling
async function createUser(prevState, formData) {
try {
const user = await createUserAPI(formData);
return { success: true, user };
} catch (error) {
return { success: false, error: error.message };
}
}
function SignupForm() {
const [state, formAction, isPending] = useActionState(createUser, null);
return (
<form action={formAction}>
<input name="email" />
<button disabled={isPending}>
{isPending ? 'Creating...' : 'Sign Up'}
</button>
{state?.error && <p className="error">{state.error}</p>}
</form>
);
}With permalink
function FeedForm() {
const [state, formAction] = useActionState(
addPost,
null,
'/feed' // Permalink for progressive enhancement
);
return <form action={formAction}>{/* ... */}</form>;
}Differences from useState
- Action-based: Updates based on form action results
- Server integration: Works seamlessly with Server Components
- Progressive enhancement: Forms work without JavaScript
- Pending state: Built-in
isPendingflag
Best Practices
- Error handling: Always handle errors in action functions
- Serializable state: State must be serializable for SSR
- Loading states: Use
isPendingfor better UX - Type safety: Use TypeScript for action signatures
<!-- Source references:
- https://react.dev/reference/react/useActionState
-->
useCallback
useCallback is a React Hook that lets you cache a function definition between re-renders. Use it to prevent child components from re-rendering unnecessarily when passing functions as props.
Usage
import { useCallback } from 'react';
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return <ShippingForm onSubmit={handleSubmit} />;
}Basic structure
const cachedFn = useCallback(fn, dependencies);fn: Function definition to cachedependencies: Array of reactive values used inside the function
Key Points
- Caches function reference: Returns same function if dependencies haven't changed
- Performance optimization only: Don't rely on
useCallbackfor correctness - Stable function identity: Useful when passing functions to memoized components
- React Compiler: Consider using React Compiler for automatic memoization
Common Patterns
Preventing child re-renders
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// Expensive component
});
function ProductPage({ productId, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', orderDetails);
}, [productId]);
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}Passing to useEffect
function ChatRoom({ roomId }) {
const createConnection = useCallback(() => {
return new Connection(roomId);
}, [roomId]);
useEffect(() => {
const connection = createConnection();
return () => connection.disconnect();
}, [createConnection]);
}Custom hook dependencies
function useData(url) {
const [data, setData] = useState(null);
const fetchData = useCallback(async () => {
const response = await fetch(url);
setData(await response.json());
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return data;
}When NOT to use useCallback
- Simple functions: Don't wrap every function
- Not passed as prop: If function isn't passed to memoized components
- Dependencies change often: Less benefit if dependencies change frequently
- For correctness: If code breaks without
useCallback, fix the underlying issue
Differences from useMemo
useCallbackcaches the function itselfuseMemocaches the result of calling the function
// useCallback - caches function
const fn = useCallback(() => doSomething(a, b), [a, b]);
// useMemo - caches result
const result = useMemo(() => doSomething(a, b), [a, b]);<!-- Source references:
- https://react.dev/reference/react/useCallback
-->
useDeferredValue
useDeferredValue is a React Hook that lets you defer updating a part of the UI. It's useful for keeping the interface responsive while showing stale content.
Usage
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<SearchInput value={query} onChange={setQuery} />
<SearchResults query={deferredQuery} />
</>
);
}Basic structure
const deferredValue = useDeferredValue(value, initialValue?);value: The value to deferinitialValue: Optional initial value for first render- Returns: Deferred version of the value
Key Points
- Shows stale content: Displays old value while new value loads
- Interruptible: Background updates can be interrupted
- Integrated with Suspense: Works seamlessly with Suspense boundaries
- No fixed delay: Updates as soon as React finishes rendering
Common Patterns
Deferring expensive updates
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<Suspense fallback={<ResultsSkeleton />}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}With initial value
function Component() {
const [value, setValue] = useState('');
const deferredValue = useDeferredValue(value, 'initial');
// First render: deferredValue is 'initial'
// Subsequent renders: deferredValue lags behind value
}Deferring object updates
function Component() {
const [options, setOptions] = useState({ filter: '', sort: 'name' });
// ✅ Good: object created outside render
const deferredOptions = useDeferredValue(options);
// ❌ Bad: new object every render
// const deferredOptions = useDeferredValue({ ...options });
}When to use useDeferredValue
Use useDeferredValue when:
- You want to show stale content while new content loads
- Updates are expensive and can be deferred
- User input should remain responsive
Don't use useDeferredValue for:
- Values that must update immediately
- Values used in text inputs
- Values that change frequently in rapid succession
Differences from useTransition
useDeferredValuedefers a valueuseTransitiondefers state updates- Both keep UI responsive but work differently
<!-- Source references:
- https://react.dev/reference/react/useDeferredValue
-->
useId
useId is a React Hook for generating unique IDs that can be passed to accessibility attributes. Use it to associate form labels with inputs, descriptions with elements, etc.
Usage
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
return (
<>
<input
type="password"
aria-describedby={passwordHintId}
/>
<p id={passwordHintId}>
Password must be at least 8 characters
</p>
</>
);
}Basic structure
const id = useId();Returns a unique ID string associated with this component instance.
Key Points
- Unique per instance: Each component instance gets a unique ID
- Stable: ID remains stable across re-renders
- Accessibility: Designed for accessibility attributes
- Not for keys: Don't use for list keys or cache keys
Common Patterns
Form labels and inputs
function FormField({ label, type }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} type={type} />
</>
);
}ARIA describedby
function InputWithHint({ hint }) {
const hintId = useId();
return (
<>
<input aria-describedby={hintId} />
<span id={hintId}>{hint}</span>
</>
);
}Multiple IDs
function Component() {
const labelId = useId();
const descriptionId = useId();
const errorId = useId();
return (
<div>
<label id={labelId}>Name</label>
<input aria-labelledby={labelId} aria-describedby={descriptionId} />
<span id={descriptionId}>Enter your full name</span>
<span id={errorId} role="alert">Error message</span>
</div>
);
}When NOT to use useId
❌ Don't use for list keys
// ❌ Bad
function List({ items }) {
return items.map(item => {
const id = useId();
return <Item key={id} item={item} />;
});
}
// ✅ Good: Use data for keys
function List({ items }) {
return items.map(item => (
<Item key={item.id} item={item} />
));
}❌ Don't use for cache keys
// ❌ Bad
function Component() {
const cacheKey = useId();
const data = useMemo(() => fetchData(), [cacheKey]);
}
// ✅ Good: Use data for cache keys
function Component({ userId }) {
const data = useMemo(() => fetchData(userId), [userId]);
}Best Practices
- One ID per use case: Create separate IDs for different purposes
- Accessibility first: Use for ARIA attributes and form associations
- Stable IDs: IDs remain stable, making them perfect for accessibility
<!-- Source references:
- https://react.dev/reference/react/useId
-->
useLayoutEffect
useLayoutEffect is a version of useEffect that fires synchronously before the browser repaints the screen. Use it when you need to measure layout or perform DOM mutations that must happen before paint.
Usage
import { useLayoutEffect, useRef, useState } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
return (
<div ref={ref} style={{ top: tooltipHeight > 100 ? 'below' : 'above' }}>
Tooltip content
</div>
);
}Basic structure
useLayoutEffect(setup, dependencies?);Same API as useEffect, but runs synchronously before browser paint.
Key Points
- Runs before paint: Executes synchronously before browser repaints
- Blocks painting: Can hurt performance if overused
- Measure layout: Use for DOM measurements that affect rendering
- Prefer useEffect: Use
useEffectwhen possible
Common Patterns
Measuring layout
function Tooltip({ children, targetRef }) {
const tooltipRef = useRef(null);
const [position, setPosition] = useState({ top: 0, left: 0 });
useLayoutEffect(() => {
const targetRect = targetRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
setPosition({
top: targetRect.bottom + 10,
left: targetRect.left + (targetRect.width - tooltipRect.width) / 2
});
}, [targetRef]);
return (
<div ref={tooltipRef} style={{ position: 'fixed', ...position }}>
{children}
</div>
);
}Preventing visual flicker
function Component() {
const [width, setWidth] = useState(0);
const ref = useRef(null);
useLayoutEffect(() => {
// Measure before paint to prevent flicker
setWidth(ref.current.offsetWidth);
}, []);
return <div ref={ref} style={{ width: width || 'auto' }}>Content</div>;
}DOM mutations before paint
function Component() {
const inputRef = useRef(null);
useLayoutEffect(() => {
// Focus input before browser paints
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}When to use useLayoutEffect
Use useLayoutEffect when:
- You need to measure DOM layout
- You need to mutate DOM before paint
- Visual flicker would occur with
useEffect
Prefer useEffect when:
- Side effects don't affect layout
- You don't need synchronous execution
- Performance is a concern
Performance Considerations
- Blocks painting: Can make app feel slow
- Use sparingly: Only when necessary
- Measure first: Profile to confirm it's needed
<!-- Source references:
- https://react.dev/reference/react/useLayoutEffect
-->
useMemo
useMemo is a React Hook that lets you cache the result of a calculation between re-renders. Use it to optimize performance by skipping expensive recalculations.
Usage
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
return <ul>{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}Basic structure
const cachedValue = useMemo(calculateValue, dependencies);calculateValue: Pure function that calculates the value (takes no arguments)dependencies: Array of reactive values used in the calculation
Key Points
- Performance optimization only: Don't rely on
useMemofor correctness - Caches calculation result: Returns cached value if dependencies haven't changed
- Pure function required: Calculation function must be pure
- React Compiler: Consider using React Compiler for automatic memoization
Common Patterns
Skipping expensive calculations
function ProductList({ products, filter }) {
const filteredProducts = useMemo(() => {
return products.filter(p => p.category === filter);
}, [products, filter]);
return <div>{filteredProducts.map(p => <Product key={p.id} {...p} />)}</div>;
}Memoizing object creation
function Component({ userId }) {
const userOptions = useMemo(() => ({
userId,
theme: 'dark',
locale: 'en'
}), [userId]);
useEffect(() => {
updateUserSettings(userOptions);
}, [userOptions]);
}Memoizing array creation
function Component({ items }) {
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
return <List items={sortedItems} />;
}Passing to memoized components
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
// Expensive rendering
});
function Parent({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
return <ExpensiveComponent data={visibleTodos} />;
}When NOT to use useMemo
- Simple calculations: Don't memoize fast operations
- Everywhere: Only use when you've identified a performance problem
- For correctness: If code breaks without
useMemo, fix the underlying issue
Performance Guidelines
1. Measure first: Use React DevTools Profiler to identify bottlenecks 2. Memoize expensive operations: Only calculations that are noticeably slow 3. Dependencies rarely change: More benefit when dependencies are stable 4. Used as dependency: When the memoized value is used in other hooks
<!-- Source references:
- https://react.dev/reference/react/useMemo
-->
useOptimistic
useOptimistic is a React Hook that lets you optimistically update the UI. Show temporary state while an action is in progress, then update with the actual result.
Usage
import { useOptimistic, useTransition } from 'react';
function LikeButton({ post }) {
const [isPending, startTransition] = useTransition();
const [optimisticLike, setOptimisticLike] = useOptimistic(post.liked);
function handleClick() {
startTransition(async () => {
setOptimisticLike(!optimisticLike);
await toggleLike(post.id);
});
}
return (
<button onClick={handleClick} disabled={isPending}>
{optimisticLike ? '❤️' : '🤍'}
</button>
);
}Basic structure
const [optimisticState, setOptimistic] = useOptimistic(value, reducer?);value: Actual value when no action is pendingreducer: Optional reducer function for complex updates- Returns:
[optimisticState, setOptimistic]
Key Points
- Optimistic updates: Show expected result immediately
- Must be in Action: Setter must be called inside
startTransition - Automatic revert: Reverts to actual value when action completes
- Better UX: Provides instant feedback to users
Common Patterns
Simple optimistic update
function LikeButton({ post }) {
const [isPending, startTransition] = useTransition();
const [optimisticLiked, setOptimisticLiked] = useOptimistic(post.liked);
function handleClick() {
startTransition(async () => {
setOptimisticLiked(!optimisticLiked);
await toggleLike(post.id);
});
}
return <button onClick={handleClick}>{optimisticLiked ? '❤️' : '🤍'}</button>;
}With reducer
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, action.todo];
case 'delete':
return state.filter(t => t.id !== action.id);
default:
return state;
}
}
function TodoList({ todos }) {
const [isPending, startTransition] = useTransition();
const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, todoReducer);
function handleAdd(text) {
startTransition(async () => {
const newTodo = { id: Date.now(), text };
setOptimisticTodos({ type: 'add', todo: newTodo });
await addTodoAPI(newTodo);
});
}
return (
<div>
{optimisticTodos.map(todo => (
<Todo key={todo.id} todo={todo} />
))}
</div>
);
}Updating counter
function LikeCounter({ likes }) {
const [isPending, startTransition] = useTransition();
const [optimisticLikes, setOptimisticLikes] = useOptimistic(likes);
function handleLike() {
startTransition(async () => {
setOptimisticLikes(likes => likes + 1);
await incrementLikes();
});
}
return (
<button onClick={handleLike}>
{optimisticLikes} likes
</button>
);
}When to use useOptimistic
Use useOptimistic when:
- Actions have predictable outcomes
- You want instant UI feedback
- Network latency affects UX
- Actions are likely to succeed
Don't use useOptimistic when:
- Actions frequently fail
- Reverting changes is confusing
- State updates are complex
Best Practices
- Use with startTransition: Always wrap in
startTransition - Handle errors: Update actual state even if action fails
- Clear feedback: Make optimistic state visually distinct if needed
- Test failures: Ensure UI handles action failures gracefully
<!-- Source references:
- https://react.dev/reference/react/useOptimistic
-->
useTransition
useTransition is a React Hook that lets you mark state updates as non-blocking transitions. Use it to keep the UI responsive during expensive updates.
Usage
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<>
{isPending && <Spinner />}
<TabButton onClick={() => selectTab('about')}>About</TabButton>
<TabButton onClick={() => selectTab('posts')}>Posts</TabButton>
</>
);
}Basic structure
const [isPending, startTransition] = useTransition();isPending: Boolean indicating if a transition is pendingstartTransition: Function to mark state updates as transitions
Key Points
- Non-blocking: Transitions don't block user interactions
- Interruptible: New updates interrupt ongoing transitions
- Loading indicators: Use
isPendingto show loading states - Not for text inputs: Can't be used to control text inputs
Common Patterns
Marking state updates as transitions
function App() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<>
{isPending && <div>Loading...</div>}
<TabButton onClick={() => selectTab('about')}>About</TabButton>
</>
);
}Preventing unwanted loading indicators
function SearchResults({ query }) {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
function updateResults(newQuery) {
startTransition(() => {
setResults(computeResults(newQuery));
});
}
// isPending stays false during transition
// User doesn't see loading spinner
return <ResultsList items={results} />;
}With async operations
function Component() {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState(null);
async function fetchData() {
startTransition(async () => {
const result = await fetch('/api/data');
// Note: Need to wrap setData in another startTransition
startTransition(() => {
setData(await result.json());
});
});
}
}When to use useTransition
Use useTransition when:
- Updating UI that's not immediately visible (tabs, filters)
- Updates can be interrupted by user interactions
- You want to keep UI responsive during expensive updates
Don't use useTransition for:
- Text inputs (use regular state updates)
- Urgent updates that must complete immediately
- Updates that should show loading indicators
Differences from startTransition
useTransitionprovidesisPendingflagstartTransitioncan be used outside components- Both mark updates as non-blocking transitions
<!-- Source references:
- https://react.dev/reference/react/useTransition
-->
createPortal
createPortal lets you render some children into a different part of the DOM. Useful for modals, tooltips, and other overlays.
Usage
import { createPortal } from 'react-dom';
function Modal({ children, isOpen }) {
if (!isOpen) return null;
return createPortal(
<div className="modal">
{children}
</div>,
document.body
);
}Basic structure
createPortal(children, domNode, key?);children: React node to renderdomNode: DOM node to render intokey: Optional key for the portal
Key Points
- Different DOM location: Renders children in different DOM node
- React tree preserved: Still part of React component tree
- Event bubbling: Events bubble according to React tree, not DOM tree
- Context access: Can access parent context
Common Patterns
Modal dialog
function Modal({ children, isOpen, onClose }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}Tooltip
function Tooltip({ children, content }) {
const [show, setShow] = useState(false);
const [position, setPosition] = useState({ top: 0, left: 0 });
const ref = useRef(null);
return (
<>
<div
ref={ref}
onMouseEnter={() => {
const rect = ref.current.getBoundingClientRect();
setPosition({ top: rect.bottom, left: rect.left });
setShow(true);
}}
onMouseLeave={() => setShow(false)}
>
{children}
</div>
{show && createPortal(
<div
className="tooltip"
style={{ position: 'fixed', ...position }}
>
{content}
</div>,
document.body
)}
</>
);
}Dropdown menu
function Dropdown({ children, menu }) {
const [isOpen, setIsOpen] = useState(false);
const buttonRef = useRef(null);
return (
<>
<button ref={buttonRef} onClick={() => setIsOpen(!isOpen)}>
{children}
</button>
{isOpen && createPortal(
<div className="dropdown-menu">
{menu}
</div>,
document.body
)}
</>
);
}Event Handling
Events from portals bubble according to React tree:
function App() {
return (
<div onClick={() => console.log('App clicked')}>
<Modal>
<button onClick={() => console.log('Button clicked')}>
Click me
</button>
</Modal>
</div>
);
// Clicking button logs both "Button clicked" and "App clicked"
}When to use createPortal
Use createPortal when:
- Rendering modals or dialogs
- Creating tooltips or popovers
- Building dropdown menus
- Need to escape parent container (z-index, overflow)
Don't use createPortal when:
- Regular component rendering is sufficient
- No need to escape parent container
- Simple nested components work fine
Best Practices
- Cleanup: Always clean up portals when component unmounts
- Accessibility: Ensure portals are accessible (focus management, ARIA)
- Event handling: Be aware of event bubbling behavior
- Performance: Don't create unnecessary portals
<!-- Source references:
- https://react.dev/reference/react-dom/createPortal
-->
createRoot
createRoot lets you create a root to display React components inside a browser DOM node. This is the modern way to render React apps.
Usage
import { createRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = createRoot(domNode);
root.render(<App />);Basic structure
const root = createRoot(domNode, options?);
root.render(reactNode);
root.unmount();domNode: DOM element to render intooptions: Optional configuration object- Returns: Root object with
renderandunmountmethods
Key Points
- Modern API: Replaces
ReactDOM.render(deprecated) - Concurrent features: Enables concurrent rendering features
- Single root: Usually one root per app
- Server rendering: Use
hydrateRootfor SSR
Common Patterns
Basic app setup
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.getElementById('root'));
root.render(<App />);With error handlers
const root = createRoot(document.getElementById('root'), {
onCaughtError: (error, errorInfo) => {
console.error('Caught error:', error);
logErrorToService(error, errorInfo);
},
onUncaughtError: (error, errorInfo) => {
console.error('Uncaught error:', error);
logErrorToService(error, errorInfo);
},
onRecoverableError: (error, errorInfo) => {
console.warn('Recoverable error:', error);
}
});
root.render(<App />);Multiple roots
// Main app
const appRoot = createRoot(document.getElementById('app'));
appRoot.render(<App />);
// Modal root
const modalRoot = createRoot(document.getElementById('modal'));
modalRoot.render(<Modal />);Unmounting
function unmountApp() {
root.unmount();
// DOM node is now empty
}Options
onCaughtError: Called when Error Boundary catches erroronUncaughtError: Called for uncaught errorsonRecoverableError: Called for recoverable errorsidentifierPrefix: Prefix for IDs generated byuseId
Differences from ReactDOM.render
- Concurrent features: Enables concurrent rendering
- Modern API: Better error handling and options
- Unmount method: Separate
unmount()method - Deprecated:
ReactDOM.renderis deprecated
Best Practices
- Single root: Use one root for entire app
- Error handling: Configure error handlers for production
- SSR: Use
hydrateRootfor server-rendered content - Portals: Use
createPortalfor modals/tooltips
<!-- Source references:
- https://react.dev/reference/react-dom/client/createRoot
-->
flushSync
flushSync lets you force React to flush any updates inside the provided callback synchronously. Use sparingly as it can hurt performance.
Usage
import { flushSync } from 'react-dom';
function Component() {
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM is updated synchronously here
console.log(document.getElementById('count').textContent);
}
return <button onClick={handleClick}>Increment</button>;
}Basic structure
flushSync(callback);callback: Function containing state updates- Forces synchronous DOM updates
Key Points
- Synchronous: Updates DOM immediately
- Performance impact: Can significantly hurt performance
- Last resort: Use only when necessary
- Third-party integration: Useful for browser API integration
Common Patterns
Browser API integration
function PrintComponent() {
const [isPrinting, setIsPrinting] = useState(false);
useEffect(() => {
function handleBeforePrint() {
flushSync(() => {
setIsPrinting(true);
});
// DOM updated before print dialog opens
}
window.addEventListener('beforeprint', handleBeforePrint);
return () => window.removeEventListener('beforeprint', handleBeforePrint);
}, []);
return <div>{isPrinting ? 'Printing...' : 'Ready'}</div>;
}Third-party library integration
function Component() {
const chartRef = useRef(null);
function updateChart(data) {
flushSync(() => {
setChartData(data);
});
// Chart library can now read updated DOM
chartRef.current.update();
}
}When to use flushSync
Use flushSync when:
- Integrating with browser APIs that need synchronous updates
- Third-party libraries require immediate DOM updates
- Print handlers need updated DOM before dialog opens
Avoid flushSync when:
- Regular React updates work fine
- Performance is a concern
- No third-party integration needed
Performance Considerations
- Blocks rendering: Forces synchronous rendering
- Disables batching: Prevents React from batching updates
- Use sparingly: Can make app feel slow
- Measure impact: Profile before and after using
Best Practices
- Last resort: Only use when absolutely necessary
- Measure performance: Profile impact on app performance
- Minimize usage: Use in smallest scope possible
- Document why: Comment why
flushSyncis needed
<!-- Source references:
- https://react.dev/reference/react-dom/flushSync
-->
hydrateRoot
hydrateRoot lets you display React components inside a browser DOM node whose HTML content was previously generated by React Server APIs. Used for server-side rendering (SSR).
Usage
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, <App />);Basic structure
const root = hydrateRoot(domNode, reactNode, options?);
root.render(reactNode);
root.unmount();domNode: DOM element with server-rendered HTMLreactNode: React component to hydrateoptions: Optional configuration
Key Points
- SSR hydration: Attaches React to server-rendered HTML
- Content matching: Server and client HTML must match
- Event handlers: Attaches event handlers to existing HTML
- Single root: Usually one root per app
Common Patterns
Basic hydration
import { hydrateRoot } from 'react-dom/client';
import App from './App';
const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, <App />);With error handlers
const root = hydrateRoot(domNode, <App />, {
onCaughtError: (error, errorInfo) => {
console.error('Caught error:', error);
logErrorToService(error, errorInfo);
},
onUncaughtError: (error, errorInfo) => {
console.error('Uncaught error:', error);
}
});Updating after hydration
const root = hydrateRoot(domNode, <App initialData={serverData} />);
// Later, update with new data
root.render(<App initialData={clientData} />);Differences from createRoot
| Feature | hydrateRoot | createRoot |
|---|---|---|
| Use case | SSR hydration | Client-only rendering |
| HTML required | Yes (server-rendered) | No |
| Attaches handlers | Yes | Creates new DOM |
| Content matching | Must match server HTML | N/A |
Best Practices
- Match content: Ensure server and client HTML match
- Error handling: Configure error handlers for production
- Identifier prefix: Use same prefix as server for
useId - Single root: Use one root for entire app
Common Issues
Hydration mismatch
// ❌ Server renders: <div>Server</div>
// ❌ Client renders: <div>Client</div>
// Error: Hydration mismatch
// ✅ Both render same content
<div>{data}</div>Missing identifier prefix
// Server
const root = hydrateRoot(domNode, <App />, {
identifierPrefix: 'app-'
});
// Client - must match
const root = hydrateRoot(domNode, <App />, {
identifierPrefix: 'app-'
});<!-- Source references:
- https://react.dev/reference/react-dom/client/hydrateRoot
-->
cache
cache lets you cache the result of a data fetch or computation in React Server Components. Prevents duplicate work across multiple components.
Usage
import { cache } from 'react';
import calculateMetrics from './lib/metrics';
const getMetrics = cache(calculateMetrics);
function Chart({ data }) {
const report = getMetrics(data);
return <div>{report.value}</div>;
}Basic structure
const cachedFn = cache(fn);fn: Function to cache- Returns: Cached version of function
- Server Components only: Only works in Server Components
Key Points
- Server Components only: Can only be used in Server Components
- Request-scoped: Cache invalidated per server request
- Memoization: Caches results based on function arguments
- Error caching: Also caches errors
Common Patterns
Caching data fetches
import { cache } from 'react';
const getUser = cache(async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
async function UserProfile({ userId }) {
const user = await getUser(userId);
return <div>{user.name}</div>;
}
async function UserAvatar({ userId }) {
const user = await getUser(userId); // Uses cached result
return <img src={user.avatar} />;
}Caching expensive computations
import { cache } from 'react';
const calculateReport = cache((data) => {
// Expensive computation
return processData(data);
});
function Report({ data }) {
const report = calculateReport(data);
return <div>{report.summary}</div>;
}Multiple cache instances
// Each cache call creates a new cache
const getUser = cache(fetchUser);
const getPost = cache(fetchPost);
// These don't share cache
const getUser2 = cache(fetchUser); // Different cacheWhen to use cache
Use cache when:
- Fetching data in Server Components
- Expensive computations in Server Components
- Multiple components need same data
- Want to avoid duplicate work
Don't use cache when:
- In Client Components (doesn't work)
- Data changes frequently
- Need request-specific data
Best Practices
- Server Components only: Only use in Server Components
- Request scope: Cache is per-request, not global
- Same function: Call
cacheonce per function - Error handling: Handle cached errors appropriately
Limitations
- Server only: Doesn't work in Client Components
- Request scope: Cache cleared between requests
- No sharing: Each
cache()call creates separate cache - Error caching: Errors are also cached
<!-- Source references:
- https://react.dev/reference/react/cache
-->
act
act is a test helper to apply pending React updates before making assertions. Wrap rendering and updates in act() to ensure updates are processed.
Usage
import { act } from 'react';
import { createRoot } from 'react-dom/client';
it('renders with button disabled', async () => {
const container = document.createElement('div');
const root = createRoot(container);
await act(async () => {
root.render(<TestComponent />);
});
expect(container.querySelector('button')).toBeDisabled();
});Basic structure
await act(async actFn);actFn: Async function wrapping renders or interactions- Ensures all updates are processed before assertions
Key Points
- Test helper: For testing React components
- Async recommended: Use
await act(async ...)pattern - Updates processed: Ensures all state updates are applied
- Library wrappers: Testing libraries often wrap this automatically
Common Patterns
Rendering components
it('renders component', async () => {
const container = document.createElement('div');
const root = createRoot(container);
await act(async () => {
root.render(<Counter />);
});
expect(container.textContent).toBe('Count: 0');
});Testing user interactions
it('increments counter on click', async () => {
const container = document.createElement('div');
const root = createRoot(container);
await act(async () => {
root.render(<Counter />);
});
const button = container.querySelector('button');
await act(async () => {
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(container.textContent).toBe('Count: 1');
});Testing async updates
it('handles async state updates', async () => {
const container = document.createElement('div');
const root = createRoot(container);
await act(async () => {
root.render(<AsyncComponent />);
await waitFor(() => {
expect(container.textContent).toContain('Loaded');
});
});
});When to use act
Use act when:
- Writing component tests
- Testing user interactions
- Testing async updates
- Using React Testing Library (automatically wrapped)
Don't use act when:
- Using React Testing Library (already wrapped)
- Testing non-React code
- Updates are synchronous and simple
Best Practices
- Always use async: Prefer
await act(async ...) - Wrap interactions: Wrap all user interactions
- Use testing libraries: Prefer React Testing Library
- One act per test: Usually one
actcall per test
Testing Library Integration
Most testing libraries wrap act automatically:
// React Testing Library - act is automatic
import { render, fireEvent } from '@testing-library/react';
it('increments counter', () => {
const { getByText } = render(<Counter />);
fireEvent.click(getByText('Increment'));
expect(getByText('Count: 1')).toBeInTheDocument();
});<!-- Source references:
- https://react.dev/reference/react/act
-->