React 19 introduced use(): a hook that reads a promise (suspending if it's pending) or a context (just like useContext). Unlike useContext, you can call use() conditionally and inside loops. That's the breakthrough — it stops being a special-case 'rules of hooks' violation.
Actions — form submissions that just work
Actions are async functions you bind to forms. React handles the pending state, errors, and optimistic updates for you. useActionState gives you the latest result + a pending flag without any of the setLoading(true); try { ... } finally { setLoading(false); } ritual.
The compiler — automatic memoization
The React Compiler auto-memoizes components and hooks at compile time, and it is past beta: version 1.0 shipped as a stable release on 2025-10-07. The era of manually wrapping everything in useMemo and useCallback is ending — but notice that ending is opt-in. cwkPippa's own Vite build does not enable the compiler today, so the memoization you see in its code, such as the message list's custom equality check, is still hand-written and still doing the work. Your code stays clean, performance stays good.
Don't over-optimize manually: If you're reaching for useMemo on something the compiler will memoize for free, you're adding noise. Profile first. Optimize only the things that show up hot.
Code
use() with a promise — inline async data·tsx
function ConversationDetail({ conversationPromise }: { conversationPromise: Promise<Conversation> }) {
// Suspends until the promise resolves; <Suspense> above renders fallback.
const conversation = use(conversationPromise);
return <ChatView conversation={conversation} />;
}
Actions — useActionState for a save form·tsx
type State = { title: string; error: string | null };
function TitleEditor({ conversationId }: { conversationId: string }) {
async function saveTitle(prevState: State, formData: FormData): Promise<State> {
const title = formData.get('title') as string;
// the route this app really uses: PATCH /api/conversations/{id}/title
const res = await fetch(`/api/conversations/${conversationId}/title`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
});
// an HTTP error is a failed save, never a success
if (!res.ok) return { title: prevState.title, error: `Save failed (${res.status})` };
return { title, error: null };
}
const [state, formAction, isPending] = useActionState(saveTitle, { title: '', error: null });
return (
<form action={formAction}>
<input name="title" defaultValue={state.title} disabled={isPending} />
<button type="submit" disabled={isPending}>{isPending ? 'Saving…' : 'Save'}</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
);
}
Progress
Progress is local-only — sign in to sync across devices.