#RR7 useRouteLoaderData

1 messages · Page 1 of 1 (latest)

south falcon
#

Is it possible to call useRouteLoaderData within a component in RR7?

We have a microserivce thats responsible for returning the menu structure for a bunch of our micro front ends. We previously included the call to fetch the menu within our root loader, but realised that on every POST request an API call was being made to fetch the menu data which isn't ideal.

I thought I might be able to move the menu call out to a separate memoized menu component and move the Menu loader to its own routes/api/menu.ts file so that I could call useRouteLoaderData('menu') within the <Menu> component

The problem is that I keep getting undefined from useRouteLoaderData. In fact, any call to useRouteLoaderData that doesn't provide root as the argument returns undefined for me.

Here is my routes.ts file, which I can confirm all return the expected loader data + views where appropriate:

import { type RouteConfig, route, index } from '@react-router/dev/routes';

export default [
  index('routes/_index.tsx'),
  // GENERAL routes
  route('health', 'routes/health.tsx'),
  route('api/report/:id', 'routes/api.report.$id.tsx'),
  route('details-server/:id', 'routes/details-server.$id.tsx'),
  route('details/:id', 'routes/details.$id.tsx'),
  route('logout', 'routes/logout.tsx'),
  route('login', 'routes/login.tsx'),
  // API routes
  route('menu', 'routes/api/menu.tsx', { id: 'menu' }),
] satisfies RouteConfig;
#

Here is my Menu component

/* eslint-disable @typescript-eslint/no-unused-vars */
import { FC, memo, useEffect, useState } from 'react';
import type {
  IControlPortnavigationProps,
} from './interface';
import { Header, Search, NavItem } from '../';
import { useFetcher, useRouteLoaderData } from 'react-router';

const Navigation: FC {
  const navigationItems = useRouteLoaderData('menu');

  return (
    <>
      <Header
        baseUrl={controlportUrl}
        toggleMenuOpen={toggleMenuOpen}
        toggleMenuHidden={toggleMenuHidden}
      />
      <Search />
      <div className="nav-blocker"></div>
      <nav className="main-nav">
        {navigationItems.map(i => <NavItem {...i} />)}
      </nav>
    </>
  );
};

export default memo(ControlPortNavigation);

Here is my routes/menu/api.ts

import { getUserFromToken } from '~/services/auth/user.server';
import { LoaderFunction } from 'react-router';
import { env } from '~/utils/env';

export const loader: LoaderFunction = async ({ request }) => {
  console.log('Fetching menu from ControlPort');

  const userData = getUserFromToken(request);

  if (!userData) {
    throw new Response('Unauthorized', { status: 401 });
  }

  const result = await fetch(
    `${env('MENU_API_ENDPOINT')}`
  );

  if (result.status !== 200) {
    console.log('___No menu, received', result.status);
    return [];
  }

  return await result.json();
};

Is what I'm trying to do possible? Or would I be better to use a fetcher and fetch + render this client side?

south falcon
#

Hrm, I guess I can move the call to fetch the menu back to the root loader and then add a cache control header so I only make a request to the microservice once per hour. Then I can consume the root loader in my component...that might be the way I have to go about it

south falcon
#

RR7 useRouteLoaderData

keen elk
#

most likely you just have the route ID wrong, but if you console.log(useMatches()) you can see the available routes and their IDs

#

ah I think the misconception here is that useRouteLoaderData doesn't trigger loaders, it just accesses their data if it's available

south falcon
#

@keen elk yeah, I noticed that I misunderstood useRouteLoaderData - I thought I could be at /posts/:id and call a separate unrelated route /api/menu to return the data just for that component

I think what I'll opt for instead is to cache the response from our menu microservice on the session so that another request isn't made to the API and then I'll keep the call to the API in my root loader so its always invoked if required

keen elk
#

yeah that sounds reasonable