Skip to main content
Version: 6.4

useSuspense()

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

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.

  • Triggers fetch:
    • On first-render
      • or parameters change
      • or required entity is deleted
      • or imperative invalidation triggered
    • and When not in cache or result is considered stale
    • and When no identical requests are in flight
    • and when params are not null
  • On Error (404, 500, etc):
  • While Loading:

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

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

function PostWithAuthor() {
const post = useSuspense(PostResource.get, { id });
// post as PostResource
const author = useSuspense(UserResource.get, {
id: post.userId,
});
// author as UserResource
}

Paginated 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[]
}
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(getTodo, id ? { id } : null);

Useful Endpoints to send

Resource provides these built-in:

Feel free to add your own RestEndpoint as well.