You are most likely aware of how to load data from API using a Loader function
const loader: LoaderFunction = async({request}) => {
const response = await some_api(url);
return json({data: response})
}
in the component you can use useLoaderData
hook to get the data.
Under the hood Remix will call the API first then render the component with the data then send it to the client (Server side data fetching)
If we were using a Vanilla React/Angular App then we would have Rendered the component first then started loading the data (Client side data fetching)
Problem with client side data fetching is that if you have nested component and each component shows a loading screen before rendering child components. Here user could have to wait for significant amount of time (more info here)
On top of that if you app has huge initial bundle size then this problem becomes even worse
So in our Remix app if we were to call all the API’s our component needs in loader function then is the problem solved ?
Well not exactly imagine a scenario where we need to call multiple API
const loader: LoaderFunction = async({request}) => {
const res1 = await api1(url1);
const res2 = await api2(url2);
const res3 = await api3(url3);
const res4 = await api4(url4);
const res5 = await api5(url5);
return json({res1, res2, res3, res4, res5})
}
Here we need to call 5 API’s due to which when user visits the page he doesn't see anything because Remix is waiting for the loader function to return data
Here we can call the API in parallel instead of calling then in series we can use Promise.all
but it accepts an array and returns an array if that is not convenient
then we can use promiseHash
from remix-utils
const loader: LoaderFunction = async ({ request }) => {
return json({
data: await promiseHash({
res1: api1(request),
res2: api2(request),
res3: api3(request),
res4: api4(request),
res5: api5(request),
}),
});
};
promiseHash
will resolve all promise in parallel
we can use .then()
to transform the data if we want to
const loader: LoaderFunction = async ({ request }) => {
return json({
data: await promiseHash({
res1: api1(request).then(foo => foo.bar).catch(e => console.error(e)),
res2: api2(request).catch(e => console.error(e)),
res3: api3(request).catch(e => console.error(e)),
res4: api4(request).catch(e => console.error(e)),
res5: api5(request).catch(e => console.error(e)),
}),
});
};
Don't forget to add a catch block
But what if this is still not enough
Is there anything we can do to show some loading state while data is being fetched on the server ?
Well thanks to React 18’s Suspense
we can stream components from the server while data is loading
to do this we can use defer
instead of json
from @remix-run/node
const loader: LoaderFunction = async ({ request }) => {
return defer({
data: promiseHash({
res1: api1(request),
res2: api2(request),
res3: api3(request),
res4: api4(request),
res5: api5(request),
}),
});
};
Here instead of resolving the promise on the server we resolve it on the client
so in the frontend we can use the data like this
const Component = () => {
const response = useLoaderData();
return <Suspense fallback={<div>Loading...</div>}>
<Await
resolve={response.data}
errorElement={<div>Oppose something went wrong</div>}
>
{(data) => (
<div>{data.res1}</div>
<div>{data.res2}</div>
<div>{data.res3}</div>
<div>{data.res4}</div>
<div>{data.res5}</div>
)}
</Await>
</Suspense>
}
Kindly read through the official guide here
Thats not it we can improve the user experience even more
the Link
and NavLink
accepts a prop prefetch
by default it is set to none
(read more here) but if we set it to intent
Remix will load the data and component when the user hovers over the link
Browser will cache the JS files and and other assets but the API calls will not be cached by default this could lead to multiple API call to our backend
So we need to cache the API response
to do this we can use isPrefetch
from remix-utils
const loader: LoaderFunction = async({request}) => {
const response = await some_api(url);
const headers = new Headers()
if (isPrefetch(request)) {
headers.set('Cache-Control', 'private, max-age=120); // expire in 2 minutes
}
return json({data: response}, {headers})
}
we can use the headers with defer
as well
In some case you can cache API response even if the request is not a prefetch
Top comments (0)