Before Suspense and use(), there was useEffect + useState. The pattern still has its place — Track 3 lesson 2 covered it. This lesson shows why we want something better.
The three-state dance
Every fetch produces three states: loading, error, success. Without Suspense, you track all three explicitly:
Plus a useEffect that orchestrates the lifecycle, plus cleanup with AbortController, plus a guard against setting state after unmount. That's a lot of boilerplate for one fetch.
The discriminated-union upgrade
Track 2 lesson 5's discriminated union shrinks the three states into one:
The component renders based on state.status; impossible combinations (loading AND data) can't exist. That's progress — but the boilerplate is still there.
The Suspense + use() upgrade
What if the loading state lived in a Suspense boundary above the component (not inside it), the error state lived in an error boundary above it, and the success path was the only branch in the component itself? That's what use() + Suspense achieves. The next four lessons assemble it piece by piece.
You're moving toward declarative data, not chasing a feature. The trajectory from useState chains → discriminated unions → Suspense + use() is the same arc: push the loading/error concerns out of the component body so the success path reads like the only path. Each step is real value.
Take the UserOld component and refactor it through the two upgrades shown: first into useFetchedUser with the discriminated union, then preview what the use() + Suspense version will look like (you'll build it for real in lesson 4). Note how much state disappears at each step.
Hint
After the discriminated-union refactor, the component is a switch statement on result.status. After the Suspense upgrade, the component is just return <p>{user.name}</p> — loading and error live in the parent.
Progress
Progress is local-only — sign in to sync across devices.