Andres Pino
Andres Pino

Reputation: 165

Can I handle multiple loaders for a single URL in Remix?

Currently I have a landing page with multiple sections, it is build with Remix.run. The sections are part of the same url, they are shown one next to each other vertically in the same page.

My problem is that the page last some time to load because there are multiple request done in the loader. Each section needs different information from the server but all the requests for all the sections are being waited in the same loader.

Can I handle a loader for each section? so the whole page is not waiting for all the requests to be done but each section can wait for its own data

I already tried to handle each section as a Route

> routes
  > my-page.tsx
  > my page
    > section-1.tsx
    > section-2.tsx

Then I tried to add a loader for each section, and also call the sections in my-page.tsx

my-page.tsx

const MyPage = (): React.ReactElement => (
  <main>
    <section>
      <Section1 />
    </section>
    <section>
      <Section2 />
    </section>
  </main>
)
export default MyPage;

But I get the next error

TypeError: Cannot destructure property 'myData' of '(0 , import_react7.useLoaderData)(...)' as it is undefined.

It seems Section1 component is trying to get the data form the MyPage loader instead of getting it from the Section1 loader itself.

If I use the <Outlet /> component in MyPage, the Section1 and Section2 loaders works if I access them through the URL

localhost:3000/my-page/section-1
localhost:3000/my-page/section-2

But I don't want the sections to be nested routes, I want them to be in my-page.

I would be very grateful if someone can help me with it. Thank you!

Upvotes: 4

Views: 14189

Answers (2)

Richard Scarrott
Richard Scarrott

Reputation: 7033

Technically you can have multiple loaders per url, however they can only be exported by a route module (A route module represents a single URL segment).

Regardless, this won't help speed up your page load because all loaders must be resolved before the page is rendered.

Therefore, the first thing I'd do is confirm that your 'sections' data is being requested in parallel.

// 🚨 Avoid this (serial)
const loader = async () => {
   const section1 = await fetchSection1();
   const section2 = await fetchSection2();
   const section3 = await fetchSection3();
   return json({ section1, section2, section3 });
}
// ✅ Prefer this (parallel)
const loader = async () => {
   const [section1, section2, section3] = await Promise.all([
      fetchSection1(),
      fetchSection2()
      fetchSection3()
   ]);
   return json({ section1, section2, section3 });
}

If it's still slow, the next step would be to figure out which section is taking a long time.

If there's one or two calls which are particularly slow, you will eventually be able to defer the data, allowing it to render a fallback until it's ready.

However at the time of writing this React Router feature hasn't landed in Remix yet.

const loader = async () => {
  // Notice we don't await fetchSection3 (as it's been identified as slow)
  const section3 = fetchSection3();
  const [section1, section2] = await Promise.all([
    fetchSection1(),
    fetchSection2(),
  ]);
  return defer({
    section1,
    section2,
    section3, // Promise
  });
};

export default function Component() {
  const { section1, section2, section3 } = useLoaderData();
  return (
    <main>
      <section>
        <Section1 data={section1} />
      </section>
      <section>
        <Section2 data={section2} />
      </section>
      <section>
        <React.Suspense fallback={"Loading"}>
          <Await resolve={section3}>
            {(section3) => (
              <Section3 data={section3} />
            )}
          </Await>
        </React.Suspense>
      </section>
    </main>
  );
}

Given defer isn't yet supported in Remix. You might have to instead create a resource route & useFetcher in the client for now.

If you find all sections are equally slow, you will need to speed up your DB / API calls. If that's not possible, your next best bet would be to look at http or server side caching.

Upvotes: 7

oscillate123
oscillate123

Reputation: 36

In remix, everything below the routes directory will be available as a "navigational route"/URL in the browser.

I would try to use <Outlet /> instead of <Section1 />. Check out this remix documentation about nested routing. I've read it a few times and sometimes look back at it just to remind myself about how Remix handles nested routing.

If you want Section1 and Section2 to appear on the same route for the user you should import react components from another place than the routes folder. Your sections might be better off as separate React Functions, imported into your route.

app/routes/my-page.tsx

import { Section1, Section2 } from "~/components/sections.tsx"
// ~ is a reference to the app directory. `~/routes` is also a valid path

// this function will be loaded when the requests comes in
export const loader: LoaderFunction = async ({request}) => {
   const myData = await fetch(random_api)
   return json({myData})
}
// this function will be returned to the browser
export default function MyPageRoute() {
   const myData = useLoaderData()
   return(
     <main>
       <section>
         <Section1 data={myData.section1} />
       </section>
       <section>
         <Section2 data={myData.section2} />
       </section>
     </main>
   )
}

I always use a directory outside of the routes/ directory for my react components, which I do not wish to be a route. Hope this helped :)

Edit - Forgot to add data props in the react components

Upvotes: 0

Related Questions