useDLE() - [D]ata [L]oading [E]rror
High performance async data rendering without overfetching. With fetch meta data.
In case you cannot use suspense, useDLE() is just like useSuspense() but returns [D]ata [L]oading [E]rror values.
useDLE()
will rerender when its data mutates.
Usage
ProfileResource
ProfileList
import { useDLE } from '@data-client/react'; import { ProfileResource } from './ProfileResource'; function ProfileList(): JSX.Element { const { data, loading, error } = useDLE(ProfileResource.getList); if (error) return <div>Error {`${error.status}`}</div>; if (loading || !data) return <>loading...</>; return ( <div> {data.map(profile => ( <div className="listItem" key={profile.pk()}> <Avatar src={profile.avatar} /> <div> <h4>{profile.fullName}</h4> <p>{profile.bio}</p> </div> </div> ))} </div> ); } render(<ProfileList />);
🔴 Live Preview
Store▶
Behavior
Expiry Status | Fetch | Data | Loading | Error | Conditions |
---|---|---|---|---|---|
Invalid | yes1 | undefined | true | false | not in store, deletion, invalidation, invalidIfStale |
Stale | yes1 | denormalized | false | false | (first-render, arg change) & expiry < now |
Valid | no | denormalized | false | maybe2 | fetch completion |
no | undefined | false | false | null used as second argument |
note
- Identical fetches are automatically deduplicated
- Hard errors to be caught by Error Boundaries
Conditional Dependencies
Use null
as the second argument on any reactive data client to indicate "do nothing."
// todo could be undefined if id is undefined
const todo = useDLE(TodoResource.get, id ? { id } : null);
Types
- Type
- With Generics
function useDLE(
endpoint: ReadEndpoint,
...args: Parameters<typeof endpoint> | [null]
): {
data: Denormalize<typeof endpoint.schema>;
loading: boolean;
error: Error | undefined;
};
function useDLE<
E extends EndpointInterface<
FetchFunction,
Schema | undefined,
undefined
>,
Args extends readonly [...Parameters<E>] | readonly [null],
>(
endpoint: E,
...args: Args
): {
data: DenormalizeNullable<typeof endpoint.schema>;
loading: boolean;
error: Error | undefined;
};
Examples
Detail
ProfileResource
ProfileDetail
import { useDLE } from '@data-client/react'; import { ProfileResource } from './ProfileResource'; function ProfileDetail(): JSX.Element { const { data: profile, loading, error, } = useDLE(ProfileResource.get, { id: 1 }); if (error) return <div>Error {`${error.status}`}</div>; if (loading || !profile) return <>loading...</>; return ( <div className="listItem"> <Avatar src={profile.avatar} /> <div> <h4>{profile.fullName}</h4> <p>{profile.bio}</p> </div> </div> ); } render(<ProfileDetail />);
🔴 Live Preview
Store▶
Conditional
null
will avoid binding and fetching data
▶Resources
▶PostWithAuthor
import { PostResource, UserResource } from './Resources'; export default function PostWithAuthor({ id }: { id: string }) { const postDLE = useDLE(PostResource.get, { id }); if (postDLE.error) return <div>Error {`${postDLE.error.status}`}</div>; if (postDLE.loading || !postDLE.data) return <>loading...</>; const authorDLE = useDLE( UserResource.get, postDLE.data.userId ? { id: postDLE.data.userId, } : null, ); if (authorDLE.error) return <div>Error {`${authorDLE.error.status}`}</div>; if (authorDLE.loading || !authorDLE.data) return <>loading...</>; return <div>{authorDLE.data.username}</div> }
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; } static key = 'PaginatedPost'; } export const getPosts = new RestEndpoint({ path: '/post', searchParams: { page: '' }, schema: { results: new schema.Collection([PaginatedPost]), nextPage: '', lastPage: '', }, });
▶ArticleList
import { useDLE } from '@data-client/react'; import { getPosts } from './api/Post'; export default function ArticleList({ page }: { page: string }) { const { data, loading, error } = useDLE(getPosts, { page }); if (error) return <div>Error {`${error.status}`}</div>; if (loading || !data) return <>loading...</>; const { results: posts, nextPage, lastPage } = data; return ( <div> {posts.map(post => ( <div key={post.pk()}>{post.title}</div> ))} </div> ); }