Development··4 min read

How React 19's use() Hook Changed My Data Fetching Patterns

One hook eliminated the entire useEffect + useState combo for me

Three useStates, One useEffect, Every Single Time

There's a pattern every React developer knows by heart for data fetching. Create data, loading, and error with useState, call fetch inside useEffect, setData in the then block, setError in catch, setLoading(false) in finally. Repeated this boilerplate in every single component. Every. Time.

After using React 19's use() hook, all of that disappeared. At first I thought "no way," but after a month of real-world usage, there's no going back.

What use() Actually Is

It's a hook that can directly read Promises. Write const data = use(somePromise) inside a component, and it enters a Suspense state until the Promise resolves, then gives you the value directly. Before, you had to handle async operations inside useEffect and manually manage state. use() lets React handle all of that for you.

How Much Code Disappeared

Comparing the user info fetching code on a profile page makes it obvious. The old way: 3 useStates, 1 useEffect, 3 conditional renders — about 25 lines. With use(): parent creates the Promise, child reads it with use(promise), done. Under 10 lines.

But the line count isn't the important part. What matters is that loading and error handling moved outside the component. Suspense and ErrorBoundary handle it automatically, so the component can focus purely on displaying data. That's the real value.

Wait, It Works Inside Conditionals?

The most radical feature of use(). Unlike other hooks, you can call it inside conditional statements. if (shouldFetch) { const data = use(promise); } — this actually works.

This was absolutely impossible under the old rules of hooks. For tab UIs where you only fetch data under certain conditions, the code becomes much more natural. Before, you'd have to create custom hooks or use enabled flags. (Which also means all those custom hooks are now unnecessary.)

Does This Kill TanStack Query?

I get this question a lot. My answer: not yet. use() specializes in reading Promises — it doesn't have caching, retries, optimistic updates, or infinite scroll. For simple data fetching, use() is plenty. But if you need complex server state management, TanStack Query still has its place. They're not competitors — they operate at different layers.

Where You Put Suspense Boundaries Is the Real Challenge

When you use use(), Suspense becomes mandatory, and where you place Suspense boundaries determines the UX. Wrap the entire page in one and everything loads together. Wrap each component individually and you get independent loading states but a jumpy UI.

I group things by "meaningful units." Header and body get separate boundaries, but charts within the body share one Suspense wrapper. This intuition is a new architectural skill that the use() era demands.

It Really Shines with Server Components

In Next.js, Server Components can use await directly, so use() isn't needed for data you can fetch on the server. Where use() shines is when a client component reads a Promise passed from a server component.

Create a fetch Promise on the server, pass it as a prop to a client component, and read it with use() on the client — you get the benefits of streaming too. The first time I used this combination, I understood why the React team built this.

Once You Try It, There's No Going Back

use() isn't a convenience feature — it changes React's data flow paradigm. It might feel unfamiliar at first, but once you internalize the idea that "async data can be used just like regular values," your component design gets cleaner. Though I'll be honest — Suspense boundary design still makes me think hard every time. That's something you can only figure out with experience.

Related Posts