Optimising Data loading in Remix
Tags: remix, react, react18, suspense
Published: 2023-08-23 13:18:44

You are most likely aware of how to load data from API using a Loader function




```ts
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](https://youtu.be/95B8mnhzoCM))

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 



```ts 
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`



```ts
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



```ts
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` 



```ts
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


```ts
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](https://remix.run/docs/en/main/guides/streaming) 

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](https://remix.run/docs/en/1.19.3/components/link#prefetch)) 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` 



```ts
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