Skip to main content

useSuspense()

High performance async data rendering without overfetching.

useSuspense() suspends rendering until the data is available. This is much like awaiting an async function. This avoids the complexity of handling loading and error conditions in your components by centralizing them with a singular AsyncBoundary.

Usage

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
import { Entity, createResource } from '@rest-hooks/rest';
export class Profile extends Entity {
id: number | undefined = undefined;
img = '';
fullName = '';
bio = '';
pk() {
return this.id?.toString();
}
}
export const ProfileResource = createResource({
path: '/profiles/:id',
schema: Profile,
});
ProfileList
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 />);
Live Preview
Loading...
Store

Behavior

Cache policy is Stale-While-Revalidate by default but also configurable.

Expiry StatusFetchSuspendErrorConditions
Invalidyes1yesnonot in store, deletion, invalidation, invalidIfStale
Staleyes1nono(first-render, arg change) & expiry < now
Validnonomaybe2fetch completion
nonononull used as second argument
note
  1. Identical fetches are automatically deduplicated
  2. 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);

Types

function useSuspense(
endpoint: ReadEndpoint,
...args: Parameters<typeof endpoint> | [null]
): Denormalize<typeof endpoint.schema>;

Examples

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
import { Entity, createResource } from '@rest-hooks/rest';
export class Profile extends Entity {
id: number | undefined = undefined;
img = '';
fullName = '';
bio = '';
pk() {
return this.id?.toString();
}
}
export const ProfileResource = createResource({
path: '/profiles/:id',
schema: Profile,
});
ProfileList
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 />);
Live Preview
Loading...
Store

Sequential

When fetch parameters depend on data from another resource.

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.

api/Post
export class PaginatedPost extends Entity {
id = '';
title = '';
content = '';
pk() {
return this.id;
}
}
export const getPosts = new RestEndpoint({
path: '/post',
searchParams: { page: '' },
schema: { results: [PaginatedPost], nextPage: '', lastPage: '' },
});
ArticleList
import { getPosts } from './api/Post';
export default function ArticleList({ page }: { page: string }) {
const {
results: posts,
nextPage,
lastPage,
} = useSuspense(getPosts, { page });
return (
<div>
{posts.map(post => (
<div key={post.pk()}>{post.title}</div>
))}
</div>
);
}

Todo App

Explore more Rest Hooks demos