useResource$()
This hook allows you to generate computed values asynchronously, typically used for fetching external data (resources.) The hook is called when the component is mounted (and when the tracked values change.) The useResource$ hook is meant to be used with the <Resource />. The <Resource /> component is a convenient way to render the resource state, such as loaded, resolved, or rejected.
The important thing to understand about useResource$ is that it executes on the initial component render (just like useTask$.) Often time it is desirable to start fetching the data on the server as part of SSR before the component is rendered. Fetching data as part of SSR is a more common and preferred way of loading data and is done through routeLoader$ API instead. useResource$ is a more low-level API that is useful when you want to fetch data on the client.
Just like all the
use-hooks, it must be called within the context ofcomponent$(), and all the hook rules apply.In many ways
useResource$is similar touseTask$The big differences are:
useResource$allows you to return a "value".useResource$does not block rendering while the resource is being resolved.See
routeLoader$for fetching data as part of SSR.
Example
Let's start with a simple example that will fetch jokes on the component's initial render.
import { component$, useResource$, Resource } from '@builder.io/qwik';
interface Joke {
value: string;
}
export default component$(() => {
const joke = useResource$(async () => {
const resp = await fetch('https://api.chucknorris.io/jokes/random');
return (await resp.json()) as Joke;
});
return (
<>
<Resource
value={joke}
onPending={() => <>loading...</>}
onResolved={(joke) => <div>{joke.value}</div>}
/>
</>
);
});
track()
A common need is to re-fetch the resource if some inputs change.
Here is a very simple example of using useResource$() to fetch a random joke:
import { component$, useResource$, Resource } from '@builder.io/qwik';
export default component$(() => {
const jokes = useResource$(async () => {
const resp = await fetch('https://api.chucknorris.io/jokes/random');
return resp.json();
});
return (
<Resource
value={jokes}
onPending={() => <div>loading...</div>}
onResolved={(joke) => (
<div>{joke.value}</div>
)}
/>
);
});
And a more complex one showcasing more features. This example will fetch a list of jokes based on the query typed by the user, automatically reacting to changes in the query, including aborting requests that are currently pending.
import {
component$,
useResource$,
Resource,
useSignal,
} from '@builder.io/qwik';
export default component$(() => {
const query = useSignal('busy');
const jokes = useResource$(async ({ track, cleanup }) => {
track(query);
// A good practice is to use `AbortController` to abort the fetching of data if
// new request comes in. We create a new `AbortController` and register a `cleanup`
// function which is called when this function re-runs.
const controller = new AbortController();
cleanup(() => controller.abort());
if (query.value.length < 3) {
return [];
}
const url = new URL('https://api.chucknorris.io/jokes/search');
url.searchParams.set('query', query.value);
const resp = await fetch(url, { signal: controller.signal });
const json = (await resp.json()) as { result: { value: string }[] };
return json.result;
});
return (
<>
Query: <input bind:value={query} />
<button>search</button>
<Resource
value={jokes}
onPending={() => <>loading...</>}
onResolved={(jokes) => (
<ul>
{jokes.map((joke) => (
<li>{joke.value}</li>
))}
</ul>
)}
/>
</>
);
});
As we see in the example above, useResource$() returns a ResourceReturn<T> object that works like a glorified, fully reactive promise, containing the data and the resource state.
The state resource.loading can be one of the following strings:
'false'- the data is not yet available.'true'- the data is available. (Either resolved or rejected.)
The callback passed to useResource$() runs right after the useTask$() callbacks complete. Please refer to the Lifecycle section for more details.
<Resource />
<Resource /> is a component meant to be used with the useResource$() that renders different content depending on if the resource is pending, resolved, or rejected.
<Resource
value={weatherResource}
onPending={() => <div>Loading...</div>}
onRejected={() => <div>Failed to load weather</div>}
onResolved={(weather) => {
return <div>Temperature: {weather.temp}</div>;
}}
/>
It is worth noting that <Resource /> is not required when using useResource$(). It is just a convenient way to render the resource state.
Examples :
-
useResource$()with<Resource />
Example showing how useResource$ is used to perform a fetch call to the agify.io API. This will guess a person's age based on the name typed by the user, and will update whenever the user types in the name input.
import {
component$,
useSignal,
useResource$,
Resource,
} from '@builder.io/qwik';
export default component$(() => {
const name = useSignal<string>();
const ageResource = useResource$<{
name: string;
age: number;
count: number;
}>(async ({ track, cleanup }) => {
track(() => name.value);
const abortController = new AbortController();
cleanup(() => abortController.abort('cleanup'));
const res = await fetch(`https://api.agify.io?name=${name.value}`, {
signal: abortController.signal,
});
return res.json();
});
return (
<div>
<h1>Enter your name, and I'll guess your age!</h1>
<input
onInput$={(e: Event) =>
(name.value = (e.target as HTMLInputElement).value)
}
/>
<Resource
value={ageResource}
onPending={() => <div>Loading...</div>}
onRejected={() => <div>Failed to person data</div>}
onResolved={(ageGuess) => {
return (
<div>
{name.value && (
<>
{ageGuess.name} {ageGuess.age} years
</>
)}
</div>
);
}}
/>
</div>
);
});