Skip to Content
React in a Week
Day 6 · Modern React 19

Day 6: Modern React 19

Suspense and use(), form Actions, optimistic UI, transitions, animated view transitions, the React Compiler and testing.

By the end of Day 5 your Reading List works: it has types, shared state, routes and server data. Today you make it feel good. You'll stop writing loading flags by hand, make the add-book form update instantly, keep the screen responsive while React does slow work, and animate the changes between screens.

These features come now, rather than on Day 2, because each one builds on things you already know: state, effects, context and async functions. They're also the newest parts of React. Most of them arrived in React 19 (December 2024), and <ViewTransition> became stable in React 19.3 (September 2026). You finish the day by switching on the React Compiler and writing your first automated tests, so you can change the app on Day 7 without being afraid of breaking it.

Time plan

LessonTimeWhat you'll do
Suspense and use()50 minRead a Promise with use() and show a fallback while it loads
Actions and forms60 minSubmit forms with Actions, show validation errors, and add items optimistically
Transitions45 minKeep typing and clicking smooth with useTransition, useDeferredValue and <Activity>
View transitions45 minAnimate items entering, leaving and moving with <ViewTransition>
Performance and the Compiler40 minMeasure first, then let the React Compiler memoize for you, and meet useEffectEvent
Testing50 minSet up Vitest and Testing Library and test a reducer and a component
Checkpoint30 minBring it all into your Reading List

That adds up to about 5 hours. If the day runs long, finish the testing lesson tomorrow morning before you start Day 7.

What you'll build today

Your Reading List gets five upgrades:

  • The add-book form uses an Action. useActionState shows validation errors, and useOptimistic puts the new book on screen immediately, before the fake server answers.
  • The detail page reads its data with use(), and a <Suspense> boundary shows "Loading details..." while it waits.
  • Switching filters is a Transition, and each card is wrapped in <ViewTransition>, so cards fade in and out instead of jumping.
  • The React Compiler is on, so you never have to write useMemo or useCallback by hand.
  • Vitest and Testing Library check BookCard and booksReducer every time you run npm test.

Lessons

Project checkpoint · Day 6 · Reading List

Today's checkpoint has two parts. The first part runs in the browser: the sandbox below holds the Day 6 version of the app. The second part (the Compiler and the tests) needs real tooling, so you do it in your own project in your editor.

What changed since Day 5

To fit in one sandbox, this version makes three simplifications. In your own project, keep what you built on Day 5.

  • No router. A selectedId state stands in for the /books/:id route. In your project, BookDetail goes inside your detail route and reads id from useParams().
  • No TanStack Query or localStorage. They don't change today, so leave them as they are in your project.
  • react-error-boundary for the error boundary. This is the same package you used on Day 5, so nothing new to learn here.

Step 1: The add-book form becomes an Action

Open AddBookForm.tsx. The controlled inputs from Day 3 are gone. The form passes formAction from useActionState to its action prop. The action validates the fields, shows the book straight away with the onOptimisticAdd function it receives as a prop, waits for saveBook, and then dispatches added inside startTransition. The submit button is its own small component, so it can call useFormStatus.

Step 2: The list shows optimistic books

ListPage in App.tsx calls useOptimistic(books, ...). While a save is pending, the new book is in the list with pending: true, so it shows faded. When the save finishes, the real book from the reducer replaces it.

Step 3: Filters switch inside a Transition, and cards animate

handleFilterChange wraps setFilter in startTransition. Each card sits inside <ViewTransition key={book.id}>, so React cross-fades cards in and out when the filter changes. Deleting a book is also a Transition, so its card fades away.

Step 4: The detail page suspends

Click Details on a book. BookExtras calls use(fetchBookDetails(book.id)). fetchBookDetails caches one Promise per id, which is important, as the first lesson explains. Click Back: because the list sits in <Activity>, your filter is still selected.

Live code· Reading List, Day 6

Try these things in the sandbox:

  1. Submit the form with an empty author. The error appears and the title you typed stays in its input.
  2. Add a real book. It appears faded at once and becomes solid about a second later.
  3. Switch between filters and watch the cards fade. If nothing animates, your browser may not support the View Transitions API yet. Everything still works; the cards change without animation.
  4. Open a book, wait for the details, go back, then open the same book again. The second time there's no loading message, because the Promise is cached.

Step 5: Turn on the React Compiler (in your project)

In your Vite project, install the compiler and add it to vite.config.ts. The performance lesson explains both setups. This one is for @vitejs/plugin-react version 6 or newer:

npm install -D --save-exact babel-plugin-react-compiler@latest
npm install -D @rolldown/plugin-babel
vite.config.ts
import { defineConfig } from 'vite';
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
import babel from '@rolldown/plugin-babel';

export default defineConfig({
  plugins: [
    react(),
    babel({
      presets: [reactCompilerPreset()],
    }),
  ],
});

Restart npm run dev, open React DevTools, and check that your components show the Memo badge.

Step 6: Add tests (in your project)

Follow the testing lesson to install Vitest and Testing Library, then add src/booksReducer.test.ts and src/BookCard.test.tsx. When you're done, this command should pass:

npm test

Done?

Your Reading List now has optimistic adds, validation errors, a suspending detail page, animated filters, compiler-optimized components and a test suite. Tomorrow you move it to Next.js, where the Actions you wrote today can run on the server.

Day 6 progress

0/7
Next: Suspense and use()

On this page