Irbisa · Cheatsheet
Hooks in Depth
Rules of hooks, custom hooks, useReducer, useContext, useMemo and useCallback, useId, useSyncExternalStore, useTransition, use
What are the rules of hooks, and why does React need them?
What is a custom hook, and when should you extract one?
function useDebouncedValue<T>(value: T, delayMs: number): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const id = setTimeout(() => setDebounced(value), delayMs); return () => clearTimeout(id);…What problem does
useContextsolve, and how do you provide and read a context?type Theme = 'light' | 'dark';const ThemeContext = createContext<Theme>('light'); function App() { const [theme, setTheme] = useState<Theme>('dark'); return (…When would you use
useReducerinstead ofuseState?type State = { items: string[]; draft: string };type Action = | { type: 'typed'; text: string } | { type: 'added' } | { type: 'removed'; index: number };…What is the difference between
useMemoandReact.memo?// React.memo: wraps a component, skips its render when props are equalconst Row = memo(function Row({ item }: { item: Item }) { return <li>{item.name}</li>;}); function ProductList({ items, query }: { items: Item[]; query: string }) {…How does React know which
useStatecall returns which state if hooks have no names or keys?What is the difference between
useMemoanduseCallback, and when are they worth using?const Chart = memo(function Chart({ points, onSelect }: ChartProps) { /* expensive SVG rendering */}); function Dashboard({ rows, filter }: { rows: Row[]; filter: string }) { const [selected, setSelected] = useState<string | null>(null);…Why does every consumer of a context re-render when its value changes, and how do you limit those re-renders?
Find the bug: the profile works for guests, but after signing in the page crashes with "Rendered more hooks than during the previous render".
function Profile() { const user = useContext(UserContext); if (!user) return <SignInPrompt />; const [orders, setOrders] = useState<Order[]>([]); useEffect(() => { fetchOrders(user.id).then(setOrders); }, [user.id]); return <OrderList user={user} orders={orders} />;}What does this log when the user clicks "+1" three times and then "Log"?
function Counter() { const [count, setCount] = useState(0); const log = useCallback(() => { console.log(count); }, []); return ( <> <button onClick={() => setCount(count + 1)}>+1</button> <button onClick={log}>Log</button> </> );}How do you combine
useReducerand context to manage a feature's state, and where does this pattern stop scaling?const CartContext = createContext<CartState | null>(null);const CartDispatchContext = createContext<Dispatch<CartAction> | null>(null); export function CartProvider({ children }: { children: React.ReactNode }) { const [cart, dispatch] = useReducer(cartReducer, { lines: [] }); return (…What is
useIdfor, and why not generate ids withMath.random()or a module counter?function EmailField() { const id = useId(); return ( <div> <label htmlFor={`${id}-email`}>Email</label> <input id={`${id}-email`} type="email" aria-describedby={`${id}-hint`} />…What do
useTransitionanduseDeferredValuedo, and when do you pick each?function Search({ items }: { items: Item[] }) { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); // lags behind while rendering const isStale = query !== deferredQuery; return (…What is the
useAPI in React 19, and how does it differ from other hooks?// PostPage.tsx, a Server Component: starts the request, does not await itexport default function PostPage({ postId }: { postId: string }) { const commentsPromise = fetchComments(postId); return ( <Suspense fallback={<Spinner />}> <Comments commentsPromise={commentsPromise} />…How would you write a
useDebouncehook for a search input?// debounced value: the usual hook for a search inputfunction useDebouncedValue<T>(value: T, delayMs: number): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delayMs); return () => clearTimeout(timer); // the next keystroke cancels the pending update…How would you write a
useLocalStoragehook, and how do you keep it in sync across tabs and safe during server rendering?function subscribe(callback: () => void) { window.addEventListener('storage', callback); // writes from other tabs window.addEventListener('local-storage', callback); // writes from this tab return () => { window.removeEventListener('storage', callback); window.removeEventListener('local-storage', callback);…What is
useSyncExternalStore, and why should external stores use it instead ofuseStateplususeEffect?function subscribe(callback: () => void) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback);…Find the bug: the cart badge freezes the tab with "The result of getSnapshot should be cached to avoid an infinite loop".
function useCartSummary() { return useSyncExternalStore(cartStore.subscribe, () => ({ count: cartStore.getState().items.length, total: cartStore.getState().total, }));} function CartBadge() { const { count, total } = useCartSummary(); return <span>{count} items, ${total}</span>;}How do you design the API of a reusable custom hook so it is easy to use and does not cause extra renders?
interface UseDisclosureOptions { defaultOpen?: boolean; onOpenChange?: (open: boolean) => void;} export function useDisclosure({ defaultOpen = false, onOpenChange }: UseDisclosureOptions = {}) {…