If you have an existing codebase full of useEffect-fetch-setState pattern, you don't need to rewrite everything at once. Here is a practical migration path:
**Step 1: Extract to a Custom Hook
Before changing the data fetching mechanism, encapsulate the exisiting pattern:
function useUser(id: string) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
setIsLoading(true);
fetchUser(id)
.then((data) => {
if (!cancelled) setUser(data);
})
.catch((err) => {
if (!cancelled) setError(err);
})
.finally(() => {
if (!cancelled) setIsLoading(false);
});
return () => {
cancelled = true;
};
}, [id]);
return { user, isLoading, error };
}
**Step 2: Add Suspense Boundaries
Wrap the consuming components in Suspense and Error Boundaries. This is safe even before switching to use(): the boundaries just don't trigger yet:
<ErrorBoundary fallback={<ErrorMessage />}>
<Suspense fallback={<Skeleton />}>
<UserProfile userId={userId} />
</Suspense>
</ErrorBoundary>
**Step 3:Swap the Internals
Now change the custom hook(or the component) to accept a promise and use use(). The consuming components don't change; they already have Suspense boundaries:
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <ProfileCard user={user} />;
}
**Step 4:Move the Fetch Up
Push promise creation to Server Components or to a caching layer. This is the real architectural shift, data initiation moves from the component that needs it to the component(or server) that can create a stable reference.
Top comments (0)