use Requires a Cached Promise

Don't use with uncached promise

A common mistake that a lot of people are making with the modern use api in react is creating a promise and wrapping it with use in the same statement. This is incorrect and actually results in an infinite loop.

As an example the following code will cause an infinite loop because of use(fetch()) style code.

lib/SquareRootResult.tsx
lib/SquareRoot.tsx
"use client";

import { fetchSqrt } from "./api";
import { use } from "react";

export const SquareRootResult = ({ input }: { input: number }) => {
const result = use(fetchSqrt(input));
return <div>{result.toFixed(2)}</div>;
};

Fix

There are a number of ways to fix this issue. A simple approach is:

  • Lift the promise into the parent component.
  • Ensure that the promise remains the same (referential equal) for a given input.

This fix is demonstrated in the following code:

lib/SquareRootResult.tsx
lib/SquareRoot.tsx
"use client";

import { use } from "react";

export const SquareRootResult = ({ input }: { input: Promise<number> }) => {
const result = use(input);
return <div>{result.toFixed(2)}</div>;
};
javascript
typescript
react
playwright

Enjoy free content straight from your inbox 💌

No spam, unsubscribe at any time.

Transcript

00:00

A common mistake that a lot of people are making with the modern use API and React is creating a promise and wrapping it in use in the same statement. This is incorrect and can actually result in an infinite loop. To demonstrate the issue, we create a simple client component that is going to accept a value from the user, and for this purpose we create a state variable followed by an input wired to that state. Finally, we pass the user selected value into a square root result component. This component is going to make an API call to compute the square root of a given input.

00:34

We make the API call using fed square root, which gives best back a promise which we unwrap using use. Now that we have the square root, we simply display it within a dev. You would think that this would work. We make an API call unwrap it with use and then display the value. However, when we look at the application within the browser, sinister things will happen. Initially it seems to work fine, but on the console you can see that the calls to fit square root are being made again and again. And in fact, if you modify the value provided to that component, the calls seem

01:06

to increase in frequency. Additionally, they get stuck in calculating and we never see the final answer. Just as a thought experiment. Based on your knowledge of react, can you guess why this is not working? The answer lies in the fact that we are creating the promise within this component, which means that every single time this component renders, it'll create a new API call. And once that call is resolved, it triggers another re agenda, which means that we are stuck in an infinite loop. There are a number of ways to fix this issue, so let's take a look at one simple approach. Instead of creating the API call, within this component,

01:40

we modify our props to be a promise of the result of this API, and within the component, we simply wait for that promise to resolve with use. That's all we need to do for this component. The next step would be to create the promise for a given value. Within the parent component. We create the promise within use memos. Whenever the value changes, we will calculate a new promise and finally pass this promise down to the child component. We are now creating the promise based on the value caching that within the parent, and then the child waits for it to resolve with use. Now, let's run the example to see if it fixes our issues.

02:13

We open up the browser and you can see that a single call to FET script was made. If you modify the value, for example, to any other thing, a new call is made, but that is all. We don't end up in an infinite loop.