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 |