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

useRouteLoaderData("root") returns undefined in root's ErrorBoundary/Layout when non-existent page is loaded right away. #9209

Closed
ansavchenco opened this issue Apr 7, 2024 · 10 comments

Comments

@ansavchenco
Copy link

ansavchenco commented Apr 7, 2024

Reproduction

I'm not sure if the behaviour is expected but this breaks the csp nonce use-case for me since it's returned as part of the root loader's data. If this is expected, how should i get the csp nonce instead?

System Info

System:
  OS: Linux 5.0 undefined
  CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
  Memory: 0 Bytes / 0 Bytes
  Shell: 1.0 - /bin/jsh
Binaries:
  Node: 18.18.0 - /usr/local/bin/node
  Yarn: 1.22.19 - /usr/local/bin/yarn
  npm: 10.2.3 - /usr/local/bin/npm
  pnpm: 8.15.3 - /usr/local/bin/pnpm
npmPackages:
  @remix-run/dev: * => 2.8.1 
  @remix-run/node: * => 2.8.1 
  @remix-run/react: * => 2.8.1 
  @remix-run/serve: * => 2.8.1 
  vite: ^5.1.0 => 5.2.8

Used Package Manager

npm

Expected Behavior

useRouteLoaderData("root") used in root's Layout/ErrorBoundary components returns root loader's data when hitting a URL with no matching route.

Actual Behavior

useRouteLoaderData("root") used in root's Layout/ErrorBoundary components returns undefined when hitting a URL with no matching route.

@ansavchenco ansavchenco changed the title useRouteLoaderData('root') returns undefined in the root's ErrorBoundary/Layout when non-existed page is loaded right away. Root route loader data is undefined in root's ErrorBoundary/Layout when non-existed page is loaded right away. Apr 7, 2024
@ansavchenco ansavchenco changed the title Root route loader data is undefined in root's ErrorBoundary/Layout when non-existed page is loaded right away. Root route loader data is undefined in root's ErrorBoundary/Layout when non-existent page is loaded right away. Apr 7, 2024
@woble
Copy link

woble commented Apr 8, 2024

This might give you the answer

#8951 (comment)

@ansavchenco
Copy link
Author

Thanks! Checked it out and it suggests to use the useRouteLoaderData which i'm already using. It returns undefined for a URL with no matching route even though the loader can still be run and return data without any errors. I have updated the Expected/Actual Behavior sections to make it more clear.

@kiliman
Copy link
Collaborator

kiliman commented Apr 8, 2024

Take a look at https://github.com/kiliman/remix-global-data

This explains the reasons and gives a possible solution.

@ansavchenco ansavchenco changed the title Root route loader data is undefined in root's ErrorBoundary/Layout when non-existent page is loaded right away. useRouteLoaderData returns undefined in root's ErrorBoundary/Layout when non-existent page is loaded right away. Apr 8, 2024
@ansavchenco ansavchenco changed the title useRouteLoaderData returns undefined in root's ErrorBoundary/Layout when non-existent page is loaded right away. useRouteLoaderData("root") returns undefined in root's ErrorBoundary/Layout when non-existent page is loaded right away. Apr 8, 2024
@ansavchenco
Copy link
Author

I couldn't find any explanation for the problem i described in the linked article. It explains why useLoaderData should not be used within ErrorBoundary and suggests to use useRouteLoaderData instead and check its result for undefined which i'm already doing. The issue is that when hitting a URL for which there is no route registered the root loader doesn't seem to run at all and therefore useRouteLoaderData("root") returns undefined.

Also, not sure the global data approach suggested by the article would work for the CSP nonce since it has to be generated on every request and not just once when the app starts.

@kiliman
Copy link
Collaborator

kiliman commented Apr 8, 2024

Remix doesn't bother calling the root loader for a route that doesn't exist. That's why even useRouteLoaderData('root') returns undefined.

What data are you trying to show in your ErrorBoundary?

@ansavchenco
Copy link
Author

Well, /non-existent is still a child of / so just logically i would expect the root loader to run for it too 🤓

  • The use-case for it is a layout that's shared between root route and root error boundary (what Layout export is for). Let's say there is a navbar with current user information. This information is loaded from the root route and in my error boundary i would still want to show that navbar so i need the root loader data.
  • Another thing is the CSP nonce which has to be provided to ScrollRestoration and Scripts components. The nonce is also loaded in the root loader. If the data is not there then the scripts execution will be blocked by CSP and Remix documentation states that at least Scripts has to be present in root error boundary.

You'll want to make sure to still render the Links, Meta, and Scripts components because the whole document will mount and unmount when the root error boundary is rendered.

Please, check the stackblitz that i provided. It highlights both of the above use-cases.

@kiliman
Copy link
Collaborator

kiliman commented Apr 8, 2024

If you need 404 to execute the root loader, add a splat route and throw a 404 response. This will force Remix to go through the usual route process.

// routes/$.tsx
export function loader() {
  throw new Response('Not Found', { status: 404 });
}

export default function Component() {
  return null;
}

https://stackblitz.com/edit/remix-run-remix-z7wwii?file=app%2Froutes%2F%24.tsx

@ansavchenco
Copy link
Author

Yep, this solved the problem for me! Thank you!
Feel free to close the issue if non-existing routes not triggering the root loader is considered working as expected.

@ansavchenco
Copy link
Author

ansavchenco commented Apr 10, 2024

Noticed one thing though. The Layout doesn't seem to help with FOUC when going back and forth between error boundary and regular content. Here is the updated stackblitz with styles to make it noticeable:
https://stackblitz.com/edit/remix-run-remix-cqfze8?file=app%2Froutes%2F_index.tsx

This probably deserves a separate issue. There is one issue closed that looks similar to what i'm experiencing - #1136 (comment)

Edited: the FOUC doesn't happen 100% of the time. Seems like it has something to do with how fast you actually click back/forward after landing on a page.

@brophdawg11
Copy link
Contributor

brophdawg11 commented Apr 10, 2024

I'm going to close this out as expected since without a splat route no route paths match so there's no root match to render. The splat route solution recommended above is the correct solution.

As for the FOUC, I believe that's expected but if you wouldn't mind opening a new issue we could use that to discuss and confirm.

From a quick look, I think it's a vite dev only issue since vite does a request for a 304 not modified response for assets in dev mode - if you do npm run build/npm run start and leverage caching headers the browser can re-use the stylesheet across the switch from UI to Error Boundary so there's no flash.

The reason the layout get's unmounted/remounted is that under the hood the React Router tree looks essentially like this:

let routes = [{
  id: 'root',
  path: '/',
  element: <Layout><RootComponent /></Layout>,
  errorElement: <Layout><RootErrorBoundary /></Layout>,
  children: ...
}];

So it's a new React.createElement call for Layout when it's being used as the component versus the error boundary. There's not really any place "above" there to use the same instance but maybe it's possible we could investigate that as part of the new issue.

The workaround for now is to use a pathless route for the error boundary which would allow the root Layout to remain mounted when going from UI to 404 routes:

root.tsx              // Layout
routes/_pathless.tsx  // ErrorBoundary
routes/_pathless._index.tsx
routes/_pathless.$.tsx

@brophdawg11 brophdawg11 closed this as not planned Won't fix, can't repro, duplicate, stale Apr 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants