Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: Parent loader data availability in child loader #9188

Closed
siric opened this issue Aug 25, 2022 · 18 comments
Closed

[Feature]: Parent loader data availability in child loader #9188

siric opened this issue Aug 25, 2022 · 18 comments
Labels

Comments

@siric
Copy link

siric commented Aug 25, 2022

What is the new or updated feature that you are suggesting?

Currently, components are able to get data from parent loaders with useRouteLoaderData. Since this is a hook, it can not be called in a loader function and so there is no way to make child fetches dependent on parent fetches.

Proposed API:

const loader = ({ routeLoaderData }) => {
  const parentData = routeLoaderData('parentId')
  ...
}

Why should this feature be included?

Many cases for dependent fetches.

@siric siric added the feature label Aug 25, 2022
@anupriya17
Copy link

We're stick with this as well

@timdorr
Copy link
Member

timdorr commented Sep 15, 2022

This is not likely to be implemented. A similar thing is answered in the Remix FAQ: https://remix.run/docs/en/v1/pages/faq#how-can-i-have-a-parent-route-loader-validate-the-user-and-protect-all-child-routes

Loaders are called in parallel, so they cannot block each other or provide each other data. In the specific case of needing an ID from a parent loader, you would get that via the URL params. E.g., /users/:userID/posts/:postID and params.userID

(Bring on the thumbs downs... 😞)

@danielhstahl
Copy link

What is the best way to handle passing access tokens then? I have a top-level "loader" that performs authentication and retrieves an access token. But the "children" loaders can't access the access token. My workaround is to use "useEffect" hooks in my children components but that robs me of all the niceties that the data loaders provide.

@machour
Copy link
Contributor

machour commented Nov 26, 2022

You could store that access token in a cookie and access it from your other loaders by reading request.headers

@moronifacundo
Copy link

https://jankraus.net/2022/04/16/access-remix-route-data-in-other-routes/

This was useful for me and I guess it will be useful to some people that get here

@qwertie
Copy link

qwertie commented Sep 18, 2023

@machour No, because the other loaders run before the access token has been retrieved by the parent loader. The fact that parents are not initialized before children is a much bigger issue than where to store info (cookies etc). The location only matters after one has dealt with ordering.

Remix FAQ says "This is probably not different than what you were doing before Remix, it might just be more obvious now". No, wrong, it's much different. Previously, when our app didn't have URL-based routing,

  1. the user had to wait for the basic UI to load before navigating to a child page, so the child page could rely on a authentication, authorization, and loading of "outer" data to have already been done.
  2. the UI used React Suspense, which you can set up so that parent stuff is loaded before child stuff begins to load.

I am baffled that this is considered unimportant in react-router... so unimportant that the documentation doesn't even mention this surprising behavior. (It's also strange that unstable_useBlocker is still unstable two years after react-router v6 is released, and that <Route loader={...}> silently fails to work outside the context of createBrowserRouter(createRoutesFromElements(...)) even though createRoutesFromElements was only meant as a temporary construct for upgrading v5 apps which didn't use loader 🤷‍♂️. And now I learn that react-router is based on "remix", but what is remix? Well if I visit the docs and click "If you're wondering what Remix is, this is the page for you" I get an error page. Luckily I can reach the same page by clicking "Technical Explanation" on the sidebar, and it tells me that Remix is "1. A compiler 2. A server-side HTTP handler 3. A server framework 4. A browser framework" and I'm thinking "Holy Moly I just wanted client-side routing, what in tarnation is all this?!</Rant> My guess: Remix is essentially react-router without the "react" part)

@janzelenka-ext54467
Copy link

this is really a huge problem. I end up with only skeletal root component (just one div + outlet), and everything in leaf routes and components. Loaders are just in leaf routes.

Only other alternative I can think up was give up on loaders and use useEffect()

@Chrissmith1991
Copy link

Chrissmith1991 commented Jan 5, 2024

Could you use react query and just pass the query client into the route loader and then fetch the data from the query cache?

@einarq
Copy link

einarq commented Feb 26, 2024

I worked around it by putting the router inside another component that I render via React.lazy.

We call a signIn method in the bootstrapping phase, prior to rendering the main React app tho, which is probably not what others are doing. We don't use a a loader to get config and token.

More or less like this:

const LazyApp = lazy(() => import('./AppContainer'));

(async () => {
      await SsoManager().signIn();

      const root = createRoot(document.querySelector('.application-wrapper')!);
        root.render(
          <Suspense fallback={<ApplicationLoader />}>
            <LazyApp />
          </Suspense>
        );
})();

@steinybot
Copy link

steinybot commented Mar 2, 2024

@timdorr this is not true:

Loaders are called in parallel, so they cannot block each other or provide each other data.

Well first of all they aren't called in parallel, they are started asynchronously which is entirely different but that's not my main gripe. Assuming that's what you meant, of course they can still block each other, that's exactly what then does. All you need to do is make sure that you start the loaders in right order. See #11319 for a proof of concept.

@brophdawg11
Copy link
Contributor

What you all want is middleware. And before that, the upcoming dataStrategy configuration option. This will let you call loaders/actions in whatever manner you want and pass data through accordingly.

Keep in mind that the RR APIs were back-ported from Remix and as we've identified Remix constraints that don't necessarily apply, we've taken steps to ease them for SPA's. In Remix, loaders are very much called in parallel - in separate HTTP requests running in different processes and potentially even on different servers, so it is impossible to provide access to other loader data.

Another approach worth considering in RR because it's all running in the browser - you can share singletons across all of your loaders so I would just create a token.js module that maintains the users token internally and exposes utilities to get the token, refresh, etc. and lets multiple instantiations hook into the same promises. Then you can do a await getToken() in every loader and it will all use the same internal token - make a single API call to refresh the token, etc.

@manzaloros
Copy link

@brophdawg11 ,

I'd love to see an example implementation of token.js for a Remix app.

Like you described, I want to be able to get a logged-in user's info from every loader / action, not just the /login route.

@brophdawg11
Copy link
Contributor

token.js was a React Router-only recommendation:

worth considering in RR because it's all running in the browser

You cannot do the same singleton in Remix because:

In Remix, loaders are very much called in parallel - in separate HTTP requests running in different processes and potentially even on different servers

Right now in Remix, I would either:

  1. check authentication in each loader/action
  2. check authentication at the adapter layer (i.e., express) and pass it in to all of your loaders via `getLoadContext``

Or wait for middleware

@samos123
Copy link

samos123 commented Jun 3, 2024

For anyone coming here, it's now part of Remix itself to do this: https://remix.run/docs/en/main/hooks/use-route-loader-data

import { useRouteLoaderData } from "@remix-run/react";

function SomeComponent() {
  const { user } = useRouteLoaderData("root");
}

image

@matthewdolman
Copy link

For anyone coming here, it's now part of Remix itself to do this: https://remix.run/docs/en/main/hooks/use-route-loader-data

import { useRouteLoaderData } from "@remix-run/react";

function SomeComponent() {
  const { user } = useRouteLoaderData("root");
}

image

This isn't what we are after, this only makes the data available in the child components, not the child loader.

@alexshelkov
Copy link

Hey, is there some news about this? Maybe I missing something but can't find correct pattern for this.

@qwertie
Copy link

qwertie commented Aug 31, 2024

@alexshelkov This was closed in 2022 as "not planned".

For what it's worth, we "solved" the issue by not using loaders. Instead we typically use effects like

    useEffect(() => {
        maybeLoadInitialStuff(model).then(() => {
            // load more stuff
        });
    }, [ stuffId ]);

where maybeLoadInitialStuff is a global function:

let loadInitialStuffPromise: Promise<void>;

export function maybeLoadInitialStuff(model: ViewModel) {
    return loadInitialStuffPromise ??= maybeLoadInitialStuffCore(model);
}

async function maybeLoadInitialStuffCore(model: ViewModel) {
    // Load stuff that everything on the page needs
}

In strict mode, using effects means that your inner stuff will be loaded twice, but there are ways around this.

@charan-mudiraj
Copy link

Complete feature for dataStrategy has been updated in react-router-dom and here is a solution i've built to solve this problem of child loaders being dependent on parent loaders. In this Solution I've utilized the handle route field and handlerCtx parameter in LoaderFunction.
I'm passing the promises of dependent loaders to child loaders by overriding their default resolution.
https://code-with-charan.hashnode.dev/handling-dependent-loaders-in-react-router-accessing-parent-loaders-promise-in-a-child-loader
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.