useSuspense()
- Type
- With Generics
function useSuspense(
endpoint: ReadEndpoint,
...args: Parameters<typeof endpoint> | [null]
): Denormalize<typeof endpoint.schema>;
function useSuspense<
E extends EndpointInterface<FetchFunction, Schema | undefined, undefined>,
Args extends readonly [...Parameters<E>] | readonly [null],
>(
endpoint: E,
...args: Args
): E['schema'] extends Exclude<Schema, null>
? Denormalize<E['schema']>
: ReturnType<E>;
Excellent for guaranteed data rendering.
useSuspense()
suspends rendering until the data is available. This is much like awaiting an async function. That is to say, the lines after the function won't be run until resolution (data is available).
Cache policy is Stale-While-Revalidate by default but also configurable.
Expiry Status | Fetch | Suspend | Error | Conditions |
---|---|---|---|---|
Invalid | yes1 | yes | no | not in store, deletion, invalidation, invalidIfStale |
Stale | yes1 | no | no | (first-render, arg change) & expiry < now |
Valid | no | no | maybe2 | fetch completion |
no | no | no | null used as second argument |
note
- Identical fetches are automatically deduplicated
- Hard errors to be caught by Error Boundaries
React Native
When using React Navigation, useSuspense() will trigger fetches on focus if the data is considered stale.
Conditional Dependencies
Use null
as the second argument on any rest hooks to indicate "do nothing."
// todo could be undefined if id is undefined
const todo = useSuspense(TodoResource.get, id ? { id } : null);
Single
Fixtures
GET /profiles/1
{"id":"1","fullName":"Einstein","bio":"Smart physicist"}
GET /profiles/2
{"id":"2","fullName":"Elon Musk","bio":"CEO of Tesla, SpaceX and owner of Twitter"}
▶api/Profile.ts
▶ProfileList.tsx
import { useSuspense } from '@rest-hooks/react';import { ProfileResource } from './api/Profile';function ProfileDetail(): JSX.Element {const profile = useSuspense(ProfileResource.get, { id: 1 });return (<div><h4>{profile.fullName}</h4><p>{profile.bio}</p></div>);}render(<ProfileDetail />);
List
Fixtures
GET /profiles
[{"id":"1","fullName":"Einstein","bio":"Smart physicist"},{"id":"2","fullName":"Elon Musk","bio":"CEO of Tesla, SpaceX and owner of Twitter"}]
▶api/Profile.ts
▶ProfileList.tsx
import { useSuspense } from '@rest-hooks/react';import { ProfileResource } from './api/Profile';function ProfileList(): JSX.Element {const profiles = useSuspense(ProfileResource.getList);return (<div>{profiles.map(profile => (<div key={profile.pk()}><h4>{profile.fullName}</h4><p>{profile.bio}</p></div>))}</div>);}render(<ProfileList />);
Sequential
function PostWithAuthor() {
const post = useSuspense(PostResource.get, { id });
// post as Post
const author = useSuspense(UserResource.get, {
id: post.userId,
});
// author as User
}
Embedded data
When entities are stored in nested structures, that structure will remain.
export class PaginatedPost extends Entity {
readonly id: number | null = null;
readonly title: string = '';
readonly content: string = '';
pk() {
return this.id;
}
}
export const getPosts = new RestEndpoint({
path: '/post\\?page=:page',
schema: { results: [PaginatedPost], nextPage: '', lastPage: '' },
});
function ArticleList({ page }: { page: string }) {
const {
results: posts,
nextPage,
lastPage,
} = useSuspense(getPosts, { page });
// posts as PaginatedPostResource[]
}
Useful Endpoint
s to send
Resource provides these built-in:
Feel free to add your own RestEndpoint as well.