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

RRR Step 3 - Hydration #4703

Merged
merged 14 commits into from Dec 14, 2022
Merged

RRR Step 3 - Hydration #4703

merged 14 commits into from Dec 14, 2022

Conversation

brophdawg11
Copy link
Contributor

@brophdawg11 brophdawg11 commented Nov 29, 2022

Initial work towards step 3 of React Router-ing Remix

  • Client hydration via createBrowserRouter/RouterProvider

Sibling RR PR: remix-run/react-router#9664

For local app testing:

  • Install latest into your local remix app
  • Remove duped dependencies
    • rm -rf node_modules/@remix-run/server-runtime/node_modules
    • rm -rf node_modules/@remix-run/react/node_modules
    • rm -rf node_modules/react-router-dom/node_modules
    • rm -rf node_modules/react-router/node_modules
  • Build this Remix branch into your local remix app:
    • REMIX_LOCAL_BUILD_DIRECTORY=../path/to/your/app yarn build
  • Build the corresponding RR branch from the above PR into your local remix app:
    • LOCAL_BUILD_DIRECTORY=../path/to/your/app yarn build
  • Run your local Remix app with npm run dev

@changeset-bot
Copy link

changeset-bot bot commented Nov 29, 2022

⚠️ No Changeset found

Latest commit: 6ed9f3f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

Comment on lines +259 to +263
_isRedirect: true,
// These were private API for transition manager that are no longer
// needed with the new router so OK to disappear
// setCookie: false,
// type: "loader",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing integration tests from dev change slightly since we use new private API on location state to detect type

"useTransition is deprecated in favor of useNavigation as of v1.9.0.",
name: "@remix-run/react",
},
],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Linter deprecation warning for useTransition - this might be 1.10.0 now though...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lets bulk include deprecation warnings in a subsequent release

Copy link
Contributor Author

Choose a reason for hiding this comment

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

3 types of deprecation warnings: eslint, type definitions (jsdoc + ts), and runtime console.warn

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Remove for now, we may add back in for the stable release

// React knows the order and handles error boundaries normally.
entryContext.appState.trackBoundaries = false;
entryContext.appState.trackCatchBoundaries = false;
router = createBrowserRouter(routes, { hydrationData });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Create our client side router singleton

}}
>
<RouterProvider router={router} fallbackElement={null} />
</RemixContext.Provider>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Render <RouterProvider> directly

@@ -764,6 +796,7 @@ import(${JSON.stringify(manifest.entry.module)});`;
/>
<script
{...props}
suppressHydrationWarning
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any reason this wasn't there already in dev? We added it while debugging some other integration test hydration failures, which I think came down to the lack of a <head> in our root.tsx but this felt like a good change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Leave this in 👍

data: match.data,
// Need to grab handle here since we don't have it at client-side route
// creation time
handle: routeModules[match.id].handle,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Grab handle from the routeModulesCache

Comment on lines +962 to +976
// Have to avoid useMemo here to avoid introducing unstable transition object
// identities in StrictMode, since navigation will be stable but using
// [navigation] as the dependency array will _still_ re-run on concurrent
// renders, and that will create a new object identify for transition
let lastNavigationRef = React.useRef<Navigation>();
let lastTransitionRef = React.useRef<Transition>();

if (lastTransitionRef.current && lastNavigationRef.current === navigation) {
return lastTransitionRef.current;
}

lastNavigationRef.current = navigation;
lastTransitionRef.current = convertNavigationToTransition(navigation);

return lastTransitionRef.current;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Concurrent mode prevents useMemo here since it re-runs even if the navigation doesn't change and creates a new transition object identity 😖

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lets convert this back to useMemo and update our integration test - but Matt/Jacob to put together a smaller reproduction to discuss separately.

return lastTransitionRef.current;
}

function convertNavigationToTransition(navigation: Navigation): Transition {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mega if/else to convert navigations to transitions

["POST", "PUT", "PATCH", "DELETE"].includes(formMethod.toUpperCase());

if (state === "idle") {
if (fetcherRR[" _hasFetcherDoneAnything "] === true) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed something here to differentiate fetcher idle/init and idle/done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the useFetcher use-case we could do this by tracking some local state (let didAnything = useState(false)).

However, in useFetchers we don't have a stable key to track fetchers so we cant do the same there.

In user application code, they can also derive this "done" state via their own useState if needed.

// we're considering adding useFetcher({ key }) anyway
// - Expose a hidden field with a stable identifier on the fetcher
// like we did for _hasFetcherDoneAnything
key: "todo-not-implemented-yet",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Router doesn't have submission keys - need to think about the best way to tackle this.

Maybe we just add a navigation key to router?

navigation = {
  state,
  location,  // has a key
  form*,
  key, // populate on _all_ navigations, submisison or not
       // does not change on redirects during a naviagtion
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be able to solve this with location.state._submissionKey which would allow us to track this key across submission redirects

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Undocumented API we can leave this out, this can be done with a hidden field in formData to track a submission. Set to key: "" for typing reasons

};
return fetcher;
} else {
invariant(false, "nope");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to self - make this more descriptive

Comment on lines +1296 to +1308
// The new router fixes a bug in useTransition where the submission
// "action" represents the request URL not the state of the <form> in
// the DOM. Back-port it here to maintain behavior, but useNavigation
// will fix this bug.
let url = new URL(formAction, window.location.origin);

// This typing override should be safe since this is only running for
// GET submissions and over in @remix-run/router we have an invariant
// if you have any non-string values in your FormData when we attempt
// to convert them to URLSearchParams
url.search = new URLSearchParams(
formData.entries() as unknown as [string, string][]
).toString();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This bug is fixed in useNavigation but we wanted to keep the bug behavior in useTransition

Comment on lines +42 to +46
// TODO: There's a bug in @remix-run/router here at the moment where the
// loader Request keeps method POST after a submission. Matt has a local
// fix but this does the trick for now. Once the fix is merged to the
// router, we can remove the isAction param and use the method here
// if (request.method !== "GET") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is no longer the case - Matt to update


// TODO: Dropped credentials:"same-origin" since it's the default
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirm this is ok

// results when we render the HTML page.
let contentType = response.headers.get("Content-Type");

if (contentType && /\bapplication\/json\b/.test(contentType)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the above TODO - in the router we use the following check - any concerns?

if (contentType && contentType.startsWith("application/json")) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any reason to do a case insensitive check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -23,7 +27,7 @@ export interface RouteModule {
| V1_HtmlMetaDescriptor
| V2_MetaFunction
| V2_HtmlMetaDescriptor[];
unstable_shouldReload?: ShouldReloadFunction;
shouldRevalidate?: ShouldRevalidateFunction;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are not keeping unstable_shouldReload - users will have to change to shouldRevalidate

}
return true;
return arg.defaultShouldRevalidate;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

shouldRevalidate replaces unstable_shouldReload

Copy link
Contributor Author

Choose a reason for hiding this comment

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

doesn't break your app - it de-optimizes your app

Comment on lines +144 to +145
// TODO: Should we `return null` here like we do for loaders? Is there
// a benefit to triggering a network request that will error/404?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirm behavior here that we want to make the fetch request that we know is going to fail with a 405. Easier for debugging?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No need to call the fetch for the 405 here can short circuit like we do for loaders


return action;
function getRedirect(response: Response): Response {
let status = parseInt(response.headers.get("X-Remix-Status")!, 10) || 302;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is new behavior to support the bug fix in the router that respects 307/308s and preserves the method/body on the redirect: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307

Remix was previously not sending the status code along at all because it became a 204

@@ -88,8 +88,6 @@ export function createStaticHandlerDataRoutes(
})
: undefined,
handle: route.module.handle,
// TODO: RRR - Implement!
shouldRevalidate: () => true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not needed on the server routes

Comment on lines -254 to 264
// TODO Do we want to log this here?
// TODO Do we want to log this here? Can we get it to not log on
// integration tests runs?
console.log("Error in entry.server handleDocumentRequest:", error);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is probably fine, can remove the todo

brophdawg11 added a commit that referenced this pull request Dec 16, 2022
Co-authored-by: Jacob Ebey <jacob.ebey@live.com>
brophdawg11 added a commit that referenced this pull request Dec 19, 2022
* Bump remix to react-router-dom@6.4.4 (#4668)
* RRR Step 2 - Server Rendering (#4669)
* RRR Step 3 - Hydration (#4703)
* RRR Rendering Follow Ups (#4885)
* ScrollRestoration on RR 6.4 (#4844)

Co-authored-by: Jacob Ebey <jacob.ebey@live.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Status: 🚀 Done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants