{"id":424,"date":"2026-07-06T05:02:14","date_gmt":"2026-07-06T05:02:14","guid":{"rendered":"https:\/\/abrarqasim.com\/blog\/react-19-use-hook-the-pattern-i-actually-reach-for-now\/"},"modified":"2026-07-06T05:02:14","modified_gmt":"2026-07-06T05:02:14","slug":"react-19-use-hook-the-pattern-i-actually-reach-for-now","status":"publish","type":"post","link":"https:\/\/abrarqasim.com\/blog\/react-19-use-hook-the-pattern-i-actually-reach-for-now\/","title":{"rendered":"React 19 use() Hook: The Pattern I Actually Reach For Now"},"content":{"rendered":"<p>Okay, confession: I ignored the <code>use()<\/code> hook for six months after React 19 shipped. I read the announcement, saw &ldquo;you can await promises in components&rdquo;, muttered &ldquo;cool&rdquo; out loud, and then went right back to writing the same <code>useEffect<\/code> + <code>useState<\/code> + <code>if (loading) return null<\/code> dance I&rsquo;ve written a hundred times. It felt like a party trick.<\/p>\n<p>Then I actually tried it on a real feature. Now I keep reaching for it, kind of by accident. This post is what I wish someone had told me before I finally sat down with the <a href=\"https:\/\/react.dev\/reference\/react\/use\" rel=\"nofollow noopener\" target=\"_blank\">React docs on <code>use<\/code><\/a>.<\/p>\n<h2 id=\"what-use-actually-does\">What use() actually does<\/h2>\n<p>The one-line version: <code>use()<\/code> unwraps a promise or a context inside your render, and lets Suspense above it show the fallback while the promise is pending. That&rsquo;s it. There&rsquo;s no new async render mode, no magic. Under the hood it&rsquo;s the same Suspense mechanism you&rsquo;ve been ignoring in the Next.js docs.<\/p>\n<p>Two things make it feel different from every other hook. You can call it inside <code>if<\/code> blocks and loops (rules of hooks got relaxed for this one), and it&rsquo;s not just for data fetching. Reading context conditionally is the other big win, and I&rsquo;ll get to that in a minute.<\/p>\n<p>The <a href=\"https:\/\/react.dev\/blog\/2024\/12\/05\/react-19\" rel=\"nofollow noopener\" target=\"_blank\">React 19 release post<\/a> is where they announce it, and it&rsquo;s short enough that I wish I&rsquo;d actually read it in December.<\/p>\n<h2 id=\"the-useeffect-dance-i-was-writing\">The useEffect dance I was writing<\/h2>\n<p>Here&rsquo;s the thing I was doing until embarrassingly recently:<\/p>\n<pre><code class=\"language-jsx\">function InvoiceView({ id }) {\n  const [invoice, setInvoice] = useState(null);\n  const [error, setError] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() =&gt; {\n    let cancelled = false;\n    setLoading(true);\n    fetch(`\/api\/invoices\/${id}`)\n      .then((r) =&gt; {\n        if (!r.ok) throw new Error(`HTTP ${r.status}`);\n        return r.json();\n      })\n      .then((data) =&gt; { if (!cancelled) setInvoice(data); })\n      .catch((e) =&gt; { if (!cancelled) setError(e); })\n      .finally(() =&gt; { if (!cancelled) setLoading(false); });\n    return () =&gt; { cancelled = true; };\n  }, [id]);\n\n  if (loading) return &lt;Spinner \/&gt;;\n  if (error) return &lt;ErrorBox error={error} \/&gt;;\n  return &lt;InvoiceBody invoice={invoice} \/&gt;;\n}\n<\/code><\/pre>\n<p>Twenty three lines to say &ldquo;get the invoice, show a spinner while it&rsquo;s coming, and don&rsquo;t set state after unmount&rdquo;. I&rsquo;ve written this pattern, or something painfully close to it, in every React app I&rsquo;ve touched since 2019. The <code>cancelled<\/code> flag is there because in strict mode dev, effects run twice and you don&rsquo;t want to overwrite fresh data with stale data. If you forget the flag, you eat a bug that only shows up once every three deploys.<\/p>\n<h2 id=\"after-use-with-a-suspense-boundary\">After: use() with a Suspense boundary<\/h2>\n<p>Here&rsquo;s the same feature with <code>use()<\/code>:<\/p>\n<pre><code class=\"language-jsx\">\/\/ The promise-returning function. Nothing React-specific.\nfunction getInvoice(id) {\n  return fetch(`\/api\/invoices\/${id}`).then((r) =&gt; {\n    if (!r.ok) throw new Error(`HTTP ${r.status}`);\n    return r.json();\n  });\n}\n\nfunction InvoiceView({ id }) {\n  const invoice = use(getInvoice(id));\n  return &lt;InvoiceBody invoice={invoice} \/&gt;;\n}\n\nfunction InvoicePage({ id }) {\n  return (\n    &lt;Suspense fallback={&lt;Spinner \/&gt;}&gt;\n      &lt;ErrorBoundary fallback={&lt;ErrorBox \/&gt;}&gt;\n        &lt;InvoiceView id={id} \/&gt;\n      &lt;\/ErrorBoundary&gt;\n    &lt;\/Suspense&gt;\n  );\n}\n<\/code><\/pre>\n<p>The spinner lives in the tree, not inside the component. The error UI lives in an error boundary, not inside the component. The component just says &ldquo;I need an invoice, give me one&rdquo;.<\/p>\n<p>There&rsquo;s a catch worth calling out. If you create the promise inside the component (<code>use(getInvoice(id))<\/code> on every render), you&rsquo;ll re-run the fetch on every re-render unless you cache it. In practice you almost always feed <code>use()<\/code> a promise that came from a stable source: a server component, a cached loader function, or a memoised value. I&rsquo;ve been burned by this exactly once, on a search input, and it was very fast and very expensive.<\/p>\n<h2 id=\"the-other-thing-use-does-conditional-context\">The other thing use() does: conditional context<\/h2>\n<p>The part I somehow didn&rsquo;t notice for months: <code>use()<\/code> also reads context. And unlike <code>useContext<\/code>, you can call it in a branch.<\/p>\n<p>Before:<\/p>\n<pre><code class=\"language-jsx\">function PriceBadge({ locale }) {\n  \/\/ If we don't need currency here, we still subscribe to it.\n  const currency = useContext(CurrencyContext);\n\n  if (!locale) return null;\n  return &lt;span&gt;{format(currency, locale)}&lt;\/span&gt;;\n}\n<\/code><\/pre>\n<p>After:<\/p>\n<pre><code class=\"language-jsx\">function PriceBadge({ locale }) {\n  if (!locale) return null;\n  const currency = use(CurrencyContext);\n  return &lt;span&gt;{format(currency, locale)}&lt;\/span&gt;;\n}\n<\/code><\/pre>\n<p>The dumb version of why this matters: <code>useContext<\/code> at the top means every provider change re-renders your component, even when you were going to bail early anyway. With <code>use()<\/code> inside the branch, the subscription only exists when it&rsquo;s actually needed. On a page with a few hundred badges and a <code>CurrencyContext<\/code> that changes when the user switches locale, that&rsquo;s the difference between a snappy switch and a noticeable jank.<\/p>\n<p>It&rsquo;s a small win. But it&rsquo;s the kind of small win I care about, because I&rsquo;ve spent enough time in <a href=\"https:\/\/abrarqasim.com\/blog\/react-suspense-in-production-what-i-actually-use-it-for\/\" rel=\"noopener\">React Suspense in production<\/a> to know that &ldquo;one context subscription per render&rdquo; adds up on complex pages.<\/p>\n<h2 id=\"where-use-falls-over\">Where use() falls over<\/h2>\n<p>I&rsquo;m not going to pretend this replaces everything.<\/p>\n<p>For mutations, <code>use()<\/code> is for reading. If you&rsquo;re posting a form, you want <a href=\"https:\/\/react.dev\/reference\/react\/useActionState\" rel=\"nofollow noopener\" target=\"_blank\"><code>useActionState<\/code><\/a> or a server action. Trying to shove a mutation into <code>use()<\/code> is like using a bookmark as a hammer.<\/p>\n<p>For chatty client-side fetches, if the component itself is creating the promise and you don&rsquo;t have a caching layer, you&rsquo;ll refetch on every re-render. Pair <code>use()<\/code> with something that memoises the promise. On the client that usually means React Query or SWR. On the server it usually means React&rsquo;s own request-scoped cache.<\/p>\n<p>Obvious but worth saying: this is a React 19 thing. If you&rsquo;re on 18 and can&rsquo;t upgrade this quarter, you don&rsquo;t get it. The rest of your team is not going to accept &ldquo;we need to upgrade React for one hook&rdquo;.<\/p>\n<p>And if you&rsquo;re deep inside a bespoke rendering setup with no Suspense boundary and no plans to add one, <code>use()<\/code> is going to be more trouble than the <code>useEffect<\/code> you already have.<\/p>\n<h2 id=\"where-im-using-it-right-now\">Where I&rsquo;m using it right now<\/h2>\n<p>Two places, honestly.<\/p>\n<p>Route-level data loading in a small Next.js app I&rsquo;m rebuilding. The layout has the Suspense boundary, each page component calls <code>use()<\/code> on the loader. No <code>loading.jsx<\/code> files, no <code>if (loading) return null<\/code>. The code deleted itself, and the new version is easier to read a month later.<\/p>\n<p>Conditional context reads inside heavy list rows. Feature-flag context, theme context, and a currency context that&rsquo;s basically read-mostly. All the &ldquo;only subscribe if you&rsquo;re going to render&rdquo; cases. On one dashboard we shaved a noticeable chunk off the re-render time for a table of 400 rows just by moving three <code>useContext<\/code> calls behind an early return.<\/p>\n<p>Where I&rsquo;m not using it: forms, mutations, anywhere I need optimistic updates. That&rsquo;s <code>useActionState<\/code> and <code>useOptimistic<\/code> territory, and I&rsquo;ll write about those separately once I&rsquo;ve shipped enough of them to have opinions worth sharing. So far the pattern I like most is passing the server action straight into <code>useActionState<\/code>, letting it own the pending state, and never touching <code>useState<\/code> for form fields at all. That&rsquo;s a post for another Tuesday.<\/p>\n<h2 id=\"what-to-do-this-week\">What to do this week<\/h2>\n<p>Pick one component in your codebase that does the useEffect + useState + &ldquo;if loading return null&rdquo; dance and rewrite it with <code>use()<\/code>. Just one. Wrap it in a Suspense boundary and an error boundary. Notice what got deleted. Notice what got harder. Both matter.<\/p>\n<p>If you want a longer read on how I think about Suspense boundaries in general (where to place them, how granular to go), that&rsquo;s what my <a href=\"https:\/\/abrarqasim.com\/blog\/react-suspense-in-production-what-i-actually-use-it-for\/\" rel=\"noopener\">React Suspense in production<\/a> writeup was about. And if you&rsquo;re the kind of person who likes to see a whole app built around these ideas, I keep a handful of them on my <a href=\"https:\/\/abrarqasim.com\/work\" rel=\"noopener\">portfolio<\/a>.<\/p>\n<p>That&rsquo;s the pattern. Try it on something small first.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The React 19 use() hook quietly replaced my useEffect+useState fetch dance and my useContext guards. Here&#8217;s what works, where it breaks, and how I ship it.<\/p>\n","protected":false},"author":2,"featured_media":423,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"The React 19 use() hook quietly replaced my useEffect+useState fetch dance and my useContext guards. Here's what works, where it breaks, and how I ship it.","rank_math_focus_keyword":"react 19 use hook","rank_math_canonical_url":"","rank_math_robots":"","footnotes":""},"categories":[138,354],"tags":[38,44,41,359,355,414,462],"class_list":["post-424","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-frontend","category-react","tag-frontend","tag-javascript","tag-react","tag-react-19-2","tag-react-hooks-2","tag-suspense","tag-use-hook"],"_links":{"self":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/424","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/comments?post=424"}],"version-history":[{"count":0,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/424\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media\/423"}],"wp:attachment":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media?parent=424"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/categories?post=424"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/tags?post=424"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}