#How do you get access to loader data once in the layout (root.tsx)?

1 messages · Page 1 of 1 (latest)

empty mason
#

Hi y'all, my root.tsx contains a header with a profile link. In it, I want to I conditionally display things based on the session info like this

  const mainPageLoaderData =
    useRouteLoaderData<MainPageLoader>('pages/MainPage')

  let isAuthenticated = mainPageLoaderData?.isAuthenticated
  // ...conditional rendering

My MainPage.tsx loader looks like this


export let loader = async ({ context }: LoaderFunctionArgs) => {
  // ... fetch user from database
    return json({
      isAuthenticated,
      currentUser,
    })
  }
}

When I go to a different route, the pages/MainPage loader doesn't run. How do you pipe session info to different places (like my layour in root.tsx) without fetching the user 50 times for each loader?

fresh summit
#

if you need it globally, do it in app/root

#

if you don't need it globally but you do need it for a group of routes, create a pathless layout route, put the loader there and render only the Outlet so you have no UI

#
app/routes/_app.tsx <- here add the loader and render only <Outlet />
app/routes/_app.MainPage.tsx <- this can now access `routes/_app` loader data
#

note that this works for data you need in the UI

#

if you need that same data in a route loader, you will need to get it again in that loader

#

loaders can't share data because they run in parallel

empty mason
#

Gotcha! Thank you!

empty mason
#

I tried something creative. I'm porting a large app that has existing auth middleware and uses graphql queries to check auth state.

In loadContext.ts I mutate the context.user object or return a response with headers that reset the cookie

export async function getLoadContext({
  request,
  context,
}: RawArg): Promise<AppLoadContext> {
  // ...
  finalContext.authResult = await authHandler(request, finalContext) // sets context.user
  finalContext.gqlClient = createGraphqlClient(finalContext) // graphql queries use context.user
  return finalContext
}

In entry.server.tsx

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
  context: AppLoadContext,
) {
  if (context.authResult) {
    // bail out of rendering the app
    return context.authResult
  }

  let app = (
    <ApolloProvider client={context.gqlClient}>
      <ThemeProvider theme={mTheme}>
        <I18nProvider locale="en-GB">
          <RemixServer context={remixContext} url={request.url} />
        </I18nProvider>
      </ThemeProvider>
    </ApolloProvider>
  )

  await getDataFromTree(app)

  let gqlState = context.gqlClient.extract()

  app = (
    <ApolloStateContext.Provider value={gqlState}>
      {app}
    </ApolloStateContext.Provider>
  )

  let markup = renderToString(app)

  responseHeaders.set('Content-Type', 'text/html')
  return new Response('<!DOCTYPE html>' + markup, {
    headers: responseHeaders,
    status: responseStatusCode,
  })
}

While it doesn't use remix loaders for everything, this pattern seems to work nicely and allows me to reuse a lot of code.

#

Of course there's the question about parallel data loading, but I just wanted to get the app running first and worry about optimization later.

fresh summit
#

unless you also enable Single Fetch, this will only work for server side rendering

#

on client-side navigation each loader will have a unique context object

shadow nimbus
#

Am usually using react context, i will create a provider eg AuthProvider and custom hook to access that context useAuth , then on root file loader i query the db to get info (auth-user then i return it and pass down to the provider finally entire app has access to the that context through custom hook,

Note: Use this approach only if you need global state

shadow nimbus
empty mason
#

I did something similar @shadow nimbus, but I provided the whole Apollo cache state and I hydrate it on every request

// entry.server.tsx
  let app = (
    <ApolloProvider client={context.gqlClient}>
      <RemixServer context={remixContext} url={request.url} />
    </ApolloProvider>
  )

  await getDataFromTree(app)

  let gqlState = context.gqlClient.extract()

  app = (
    <ApolloStateContext.Provider value={gqlState}>
      {app}
    </ApolloStateContext.Provider>
  )
// root.tsx
export function Layout({ children }: { children: React.ReactNode }) {
  let initialState = useContext(ApolloStateContext)

  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        // ...
        <ScrollRestoration />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.__APOLLO_STATE__=${JSON.stringify(
              initialState,
            ).replace(/</g, '\\u003c')};`,
          }}
        />
        <Scripts />
      </body>
    </html>
  )
}

Then when I create my Apollo cache I run

  if (typeof window !== 'undefined' && window.__APOLLO_STATE__) {
    cache = cache.restore(window.__APOLLO_STATE__)
  }