Skip to content

Use granular route.lazy for loaders/actions #13339

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

Merged

Conversation

markdalgleish
Copy link
Member

@markdalgleish markdalgleish commented Apr 2, 2025

Now that we have granular route.lazy.loader and route.lazy.action functions, we should only block on the required function before calling the handler rather than waiting on the entire route module to load.

TODO:

  • Initial implementation with all existing tests passing
  • loadLazyRoute should accept the type arg ("loader" | "action") and return the specific handler promise rather than all property promises since the rest aren't used
  • Add tests that cover lazy loaders/actions running before other lazy properties have finished loading
  • Add tests that cover errors thrown by non-handler lazy functions

Copy link

changeset-bot bot commented Apr 2, 2025

🦋 Changeset detected

Latest commit: 7ff03a1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
react-router Patch
@react-router/architect Patch
@react-router/cloudflare Patch
@react-router/dev Patch
react-router-dom Patch
@react-router/express Patch
@react-router/node Patch
@react-router/serve Patch
@react-router/fs-routes Patch
@react-router/remix-routes-option-adapter Patch
create-react-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@markdalgleish markdalgleish changed the title WIP: Use granular route.lazy for loaders/actions Use granular route.lazy for loaders/actions Apr 3, 2025
@markdalgleish markdalgleish marked this pull request as ready for review April 3, 2025 02:46
Comment on lines -225 to -233
// @ts-expect-error
caseSensitive: async () => true,
// @ts-expect-error
path: async () => "/lazy/path",
// @ts-expect-error
id: async () => "lazy",
// @ts-expect-error
index: async () => true,
// @ts-expect-error
Copy link
Member Author

@markdalgleish markdalgleish Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These directives were unused because of the // @ts-expect-error above lazy.

Comment on lines -5268 to -5270
m.route.lazy
? loadLazyRoute(m.route, manifest, mapRouteProperties)
: undefined
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check for m.route.lazy was redundant since loadLazyRoute also performs this check internally. Removing this ternary made managing the types a lot simper since loadLazyRoute now returns an object of promises rather than a single promise.

Comment on lines +5374 to +5390
async function callLoaderOrAction({
type,
request,
match,
lazyHandlerPromise,
lazyRoutePromise,
handlerOverride,
scopedContext,
}: {
type: "loader" | "action";
request: Request;
match: AgnosticDataRouteMatch;
lazyHandlerPromise: Promise<void> | undefined;
lazyRoutePromise: Promise<void> | undefined;
handlerOverride: Parameters<DataStrategyMatch["resolve"]>[0];
scopedContext: unknown;
}): Promise<DataStrategyResult> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swapped positional args out for an object here because we now have two args the same type (lazyHandlerPromise and lazyRoutePromise), so I wanted to be extra sure that we don't accidentally get the order wrong.

}

// We use `.then` to chain additional logic to the lazy route promise so that
// the consumer's lazy route logic is coupled to our logic for updating the
// route in place in a single task. This ensures that the cached promise
// contains all logic for managing the lazy route. This chained promise is
// then awaited so that consumers of this function see the updated route.
let lazyRoutePromise = route.lazy().then((lazyRoute) => {
let lazyRoutePromise = (async () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since loadLazyRoute is no longer an async function, and is instead a sync function that returns an object with multiple promises, we need to wrap the route.lazy() call within an async IIFE so that errors are handled correctly.

Comment on lines +5076 to +5077
lazyRoutePromise,
lazyHandlerPromise,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily for this PR but should we be trying to cache/dedupe these as well?

@brophdawg11 brophdawg11 changed the base branch from dev to release-next April 3, 2025 14:01
@brophdawg11 brophdawg11 merged commit 57873d1 into release-next Apr 3, 2025
5 checks passed
@brophdawg11 brophdawg11 deleted the markdalgleish/granular-route-lazy-loaders-actions branch April 3, 2025 14:43
Copy link
Contributor

github-actions bot commented Apr 3, 2025

🤖 Hello there,

We just published version 7.5.0-pre.1 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

Copy link
Contributor

github-actions bot commented Apr 4, 2025

🤖 Hello there,

We just published version 7.5.0 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

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

Successfully merging this pull request may close these issues.

2 participants