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()
is reactive to data mutations; rerendering only when necessary.
Usage
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 />);
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 |
- Identical fetches are automatically deduplicated
- Hard errors to be caught by Error Boundaries
When using React Navigation, useDLE() will trigger fetches on focus if the data is considered stale.
Use null
as the second argument to any Data Client hook means "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
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 />);
Conditional
null
will avoid binding and fetching data
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.
export class PaginatedPost extends Entity { id = ''; title = ''; content = ''; static key = 'PaginatedPost'; } export const getPosts = new RestEndpoint({ path: '/post', searchParams: { page: '' }, schema: { results: new schema.Collection([PaginatedPost]), nextPage: '', lastPage: '', }, });
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> ); }
Github Reactions
useDLE()
allows us to declaratively fetch reactions on any issue page the moment we navigate to it. This allows
us to not block the issues page from showing if the reactions are not completed loading.
It's usually better to wrap cases like this in new Suspense Boundaries.
However, our component library ant design
does not allow this.