Reputation: 741
I havn't found any documentation or examples to explain this but from what I understand to use suspense you must add a Suspense boundary to the parent component and remove async/await from the child component and lazy load it. But when removing async/await it breaks types because everything is a promise.
For example changing await (await fetch('some-api')).json();
to fetch('some-api').json()
gives the error .json is not a function
.
Values also cannot be typed because const data: MyData = fetchData()
will error because fetchData
returns Promise<MyData>
.
With Suspense
function Parent() {
return (
<Suspense fallback={<Spinner />}>
<Child />
</Suspense>
);
}
function Child() {
const data: MyData[] = fetchData(); //Error: Type 'Promise<MyData[]>' is missing the following properties from type 'MyData[]': length, pop, push, concat, and 29 more
return (
<div>{data.map(d => <p>d.name</p>)}</data>
);
}
Before Suspense
function Child() {
const [data, setData] = useState<MyData[]>([]);
useEffect(() => {
async function loadData() {
setData(await fetchData());
}
loadData();
}, []);
if (!data.length) return <Spinner />;
return (
<div>{data.map(d => <p>d.name</p>)}</data>
);
}
Upvotes: 4
Views: 3446
Reputation: 160043
TL;DR - fetchData
's type signature would need to be just Data
, not Promise<Data>
(because it's actually fetchData(): Data with effect ReactSuspense
or fetchData(): Data throws Promise<Data>
)
Suspense doesn't make async code block, it hides the async behind indirection. Effectively, it's trying to introduce preemptive scheduling (where the runtime can stop your code at any time and switch to running a different bit of code) into a cooperatively scheduled environment (where the runtime won't stop your code until your code hits a point you've explicitly annotated with "and I yield the floor").
How it works at a hand-wavey level:
<Child />
Child
you call fetchData
fetchData
must signal React in some way "hey, I need you to interrupt Child
because we need to wait for some condition before Child
can be resumed".
throw
and catch
are the only signaling mechanism that JavaScript providesfetchData
throws a promise and React's runtime catches it. This means that Child
stops executing (it's preempted). React adds a then
listener to fetchData
's promise that will put Child
back on the queue of items to be "rendered" later.Child
. fetchData
needs to have cached the result of the promise so that when it is next called with the same arguments it will return the cached results synchronously.fetchData
is not (as you would expect) Promise<Data>
but instead JUST Data
.Upvotes: 1