
The Rule of Hooks Is Over: Mastering React 19’s `use` Function
React 19's most subversive feature allows you to consume data and context conditionally, fundamentally changing how we structure components.
The Rule of Hooks Is Over: Mastering React 19’s use Function
I remember the first time I tried to call a hook inside an if statement and the ESLint plugin screamed at me like I’d just insulted its mother. We’ve spent years conditioned to believe that hooks must live at the top level of our components—unconditional, immutable, and strictly ordered. React 19 just walked into the room and broke that glass.
The new use function is a bit of a weirdo. It’s not technically a hook, but it does hook things. It’s a multi-tool that handles both Promises and Context, and most importantly, it doesn’t care about your conditional logic.
The End of the "Top Level" Tyranny
For years, the "Rules of Hooks" were the Ten Commandments of React development. You didn’t put useContext inside a conditional block. If you needed data based on a prop, you either fetched it in a parent or lived with the mental gymnastics of useEffect.
With use, that’s gone. You can call it inside if statements, for loops (with some caveats), and after return statements.
Consuming Context Conditionally
Let’s look at a classic problem: showing a "Premium" UI only if a user is logged in. Before, you’d have to call useContext at the top, even if the user was a guest, just to satisfy the compiler.
import { use } from 'react';
import { AuthContext } from './context';
function Dashboard({ isPublic }) {
// Previously, this would be impossible.
// We can now skip context consumption entirely if we don't need it.
if (isPublic) {
return <p>Welcome to the public landing page.</p>;
}
const user = use(AuthContext);
return (
<section>
<h1>Welcome back, {user.name}!</h1>
<p>Your secret API key is: {user.apiKey}</p>
</section>
);
}This might look trivial, but it’s a massive win for performance and code organization. You aren't forcing the component to subscribe to context changes if the logic determines it doesn’t need that data.
Resolving Promises Without the Boilerplate
If you’ve ever written a useEffect that sets a loading state, then a data state, then catches an error state... I’m sorry. We’ve all been through it.
React 19 wants you to stop doing that. By passing a Promise directly to use, React will handle the "waiting" for you. It integrates directly with <Suspense> and ErrorBoundary.
import { use, Suspense } from 'react';
// Imagine this is a function that returns a fetch promise
const dataPromise = fetchInventory();
function InventoryList() {
// No useEffect. No useState.
// React suspends this component until the promise resolves.
const items = use(dataPromise);
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name} - ${item.price}</li>
))}
</ul>
);
}
export default function App() {
return (
<Suspense fallback={<p>Loading our inventory...</p>}>
<InventoryList />
</Suspense>
);
}The "Gotcha" with Promises
There is one big rule you can't ignore: Don't create the promise inside the component.
If you call fetchInventory() inside the component body, it will trigger a new fetch on every single render, causing an infinite loop of "fetch -> suspend -> re-render -> fetch." You should either pass the promise down from a Server Component or define it outside the render cycle.
Why this is a paradigm shift
The introduction of use signifies a shift in how React handles data flow. We are moving away from declarative state management (setting isLoading to true) toward native orchestration.
I’ve found that this pattern makes components significantly more readable. You read the code from top to bottom, and when the code needs data, it just... asks for it. If the data isn't there, React pauses execution, goes and gets it, and comes back when it's ready. No more "spinner hell" logic scattered across ten different lines of code.
The "Can I Use It Everywhere?" Test
You might be wondering: "If use can be conditional, why can't useState?"
The technical reason is that use doesn't rely on the order of calls to track state. Context is looked up via the tree, and Promises are tracked by their reference. useState and useEffect still rely on a specific call order to know which state variable belongs to which slot in React's internal memory.
So no, the "Rules of Hooks" aren't *dead* for everything—but they’ve definitely lost their absolute authority.
Practical Advice for Transitioning
1. Start with Context: It’s the easiest way to get used to use. Look for components where you’re currently using useContext but only using the value in specific branches of your code.
2. Use Suspense Boundaries: If you’re going to use use with Promises, make sure your <Suspense> boundaries are granular. Don't wrap your entire app in one spinner; wrap the small, slow parts.
3. Clean up your Imports: You can finally delete those giant blocks of useEffect that are just doing data fetching. Your future self (and your teammates) will thank you for the reduced cognitive load.
React 19 isn't just a performance update; it's a cleanup crew for the ceremony and boilerplate we've been tolerating for years. The use function is the star of that show. Use it wisely, and stop letting ESLint tell you how to write your if statements.


