React 19 keeps every hook from React 18 and adds six more, so you now have 17 built-in hooks at your fingertips.
Below, each hook is grouped by job, explained in one sentence, and paired with a tiny, real-world TypeScript snippet that runs in any Vite, CRA, or Next.js starter.
🧩 1. State Hooks
| Hook | Why it exists | 4-line demo |
|---|
| useState | Track local mutable state | const [n,setN]=useState(0); |
| useReducer | Handle complex or multi-step state | const [s,d]=useReducer(reducer,0); |
| useActionState (NEW) | Built-in state machine for forms & server actions | [see below] |
// useActionState – contact form with loading & success
import { useActionState } from 'react';
async function send(prev: any, fd: FormData) {
await fetch('/api/contact', { method:'POST', body: fd });
return { ok: true };
}
export default function Contact() {
const [msg, submit] = useActionState(send, {});
return (
<form action={submit}>
<input name="email" required />
<button disabled={msg.ok}>{msg.ok ? 'Sent ✔' : 'Send'}</button>
</form>
);
}
📦 2. Ref Hooks
| Hook | Why it exists | 4-line demo |
|---|
| useRef | Hold a mutable box across renders | const r=useRef(null); |
| useImperativeHandle | Let parent call child methods via ref | [see below] |
// useImperativeHandle – parent can call .focus()
import { forwardRef, useRef, useImperativeHandle } from 'react';
const FancyInput = forwardRef<HTMLInputElement>((_, ref) => {
const inner = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({ focus: () => inner.current?.focus() }));
return <input ref={inner} placeholder="Focus me from parent" />;
});
export default function Parent() {
const ref = useRef<{ focus(): void }>(null);
return (
<>
<FancyInput ref={ref} />
<button onClick={() => ref.current?.focus()}>Focus</button>
</>
);
}
⚡ Effect & Lifecycle Hooks
| Hook | Why it exists | 4-line demo |
|---|
| useEffect | Run side-effects after paint | useEffect(()=>console.log('mount'),[]); |
| useLayoutEffect | Run synchronously before paint | useLayoutEffect(()=>measure(),[dep]); |
| useInsertionEffect (NEW) | Inject global styles/scripts before paint | [see below] |
// useInsertionEffect – add CSS-in-JS without FOUC
import { useInsertionEffect } from 'react';
export default function Button({ color }: { color: string }) {
useInsertionEffect(() => {
const style = document.createElement('style');
style.innerHTML = `.btn-${color}{background:${color};}`;
document.head.append(style);
return () => style.remove();
}, [color]);
return <button className={`btn-${color}`}>Click</button>;
}
🧪 Transition & Defer Hooks
| Hook | Why it exists | 4-line demo |
|---|
| useTransition | Keep UI responsive during heavy updates | [see below] |
| useDeferredValue | Defer expensive child renders | [see below] |
// useTransition – switch tab without freezing UI
import { useState, useTransition } from 'react';
export default function Tabs() {
const [tab, setTab] = useState('home');
const [pending, start] = useTransition();
return (
<>
<button onClick={() => start(() => setTab('settings'))}>
Settings
</button>
{pending && <span>Loading…</span>}
{tab === 'settings' && <h2>Settings ⚙️</h2>}
</>
);
}
// useDeferredValue – search stays fast while 10 k rows render
import { useState, useDeferredValue, memo } from 'react';
export default function App() {
const [text, setText] = useState('');
const deferred = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<BigList filter={deferred} />
</>
);
}
const BigList = memo(({ filter }: { filter: string }) => (
<ul>
{Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
.filter(it => it.includes(filter))
.map(it => <li key={it}>{it}</li>)}
</ul>
));
| Hook | Why it exists | 4-line demo |
|---|
| useFormState | Centralised form state | [state,action]=useFormState(fn,{}); |
| useFormStatus | Know if any parent form is submitting | [see below] |
// useFormStatus – reusable SubmitButton
import { useFormStatus } from 'react-dom';
export function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Saving…' : 'Save'}</button>;
}
🎭 Optimistic UI Hook (NEW)
| Hook | Why it exists | 4-line demo |
|---|
| useOptimistic | Show instant result, roll back on error | [see below] |
// useOptimistic – like counter with instant +1, rollback on fail
import { useOptimistic, useState } from 'react';
export default function Like({ id, initial }: { id: string; initial: number }) {
const [likes, setLikes] = useState(initial);
const [opt, add] = useOptimistic(likes, l => l + 1);
async function like() {
add();
try {
await fetch(`/api/like/${id}`, { method:'POST' });
setLikes(l => l + 1);
} catch { /* auto-rollback */ }
}
return <button onClick={like}>{opt} ❤️</button>;
}
🌐 Promise Hook (NEW)
| Hook | Why it exists | 4-line demo |
|---|
| use | Await promises inside components | [see below] |
// use() – fetch without useEffect
import { use } from 'react';
async function getUser(id: string) {
const res = await fetch(`/api/user/${id}`);
return res.json();
}
export default function Profile({ id }: { id: string }) {
const user = use(getUser(id));
return <h1>Hi {user.name}</h1>;
}
🔄 Memoization Hooks
| Hook | Why it exists | 4-line demo |
|---|
| useMemo | Cache expensive values | const m=useMemo(()=>heavy(v),[v]); |
| useCallback | Cache function references | const cb=useCallback(fn,[dep]); |
🧩 Context Hook
| Hook | Why it exists | 4-line demo |
|---|
| useContext | Consume context values | const theme=useContext(ThemeCtx); |
🪝 Debug Hook
| Hook | Why it exists | 4-line demo |
|---|
| useDebugValue | Label custom hooks in DevTools | useDebugValue(Count:${count}); |
✅ TL;DR
| Category | Hooks |
|---|
| State | useState, useReducer, useActionState |
| Ref | useRef, useImperativeHandle |
| Effect | useEffect, useLayoutEffect, useInsertionEffect |
| Transition | useTransition, useDeferredValue |
| Form & Optimistic | useFormState, useFormStatus, useOptimistic |
| Promise | use |
| Memoization | useMemo, useCallback |
| Context | useContext |
| Debug | useDebugValue |