Skip to content

Commit

Permalink
Ensure consistent component tree for useId (#9805)
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Jan 4, 2023
1 parent b5d10b9 commit ee2fc0c
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 51 deletions.
6 changes: 6 additions & 0 deletions .changeset/three-ladybugs-clap.md
@@ -0,0 +1,6 @@
---
"react-router": patch
"react-router-dom": patch
---

Ensure useId consistency during SSR
1 change: 0 additions & 1 deletion packages/react-router-dom/index.tsx
Expand Up @@ -178,7 +178,6 @@ export {
export {
UNSAFE_DataRouterContext,
UNSAFE_DataRouterStateContext,
UNSAFE_DataStaticRouterContext,
UNSAFE_NavigationContext,
UNSAFE_LocationContext,
UNSAFE_RouteContext,
Expand Down
28 changes: 12 additions & 16 deletions packages/react-router-dom/server.tsx
Expand Up @@ -27,7 +27,6 @@ import {
Router,
UNSAFE_DataRouterContext as DataRouterContext,
UNSAFE_DataRouterStateContext as DataRouterStateContext,
UNSAFE_DataStaticRouterContext as DataStaticRouterContext,
UNSAFE_enhanceManualRouteObjects as enhanceManualRouteObjects,
} from "react-router-dom";

Expand Down Expand Up @@ -98,6 +97,7 @@ export function StaticRouterProvider({
router,
navigator: getStatelessNavigator(),
static: true,
staticContext: context,
basename: context.basename || "/",
};

Expand All @@ -119,22 +119,18 @@ export function StaticRouterProvider({

return (
<>
<DataStaticRouterContext.Provider value={context}>
<DataRouterContext.Provider value={dataRouterContext}>
<DataRouterStateContext.Provider
value={dataRouterContext.router.state}
<DataRouterContext.Provider value={dataRouterContext}>
<DataRouterStateContext.Provider value={dataRouterContext.router.state}>
<Router
basename={dataRouterContext.basename}
location={dataRouterContext.router.state.location}
navigationType={dataRouterContext.router.state.historyAction}
navigator={dataRouterContext.navigator}
>
<Router
basename={dataRouterContext.basename}
location={dataRouterContext.router.state.location}
navigationType={dataRouterContext.router.state.historyAction}
navigator={dataRouterContext.navigator}
>
<Routes />
</Router>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
</DataStaticRouterContext.Provider>
<Routes />
</Router>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
{hydrateScript ? (
<script
suppressHydrationWarning
Expand Down
1 change: 0 additions & 1 deletion packages/react-router-native/index.tsx
Expand Up @@ -125,7 +125,6 @@ export {
export {
UNSAFE_DataRouterContext,
UNSAFE_DataRouterStateContext,
UNSAFE_DataStaticRouterContext,
UNSAFE_NavigationContext,
UNSAFE_LocationContext,
UNSAFE_RouteContext,
Expand Down
2 changes: 0 additions & 2 deletions packages/react-router/index.ts
Expand Up @@ -76,7 +76,6 @@ import type {
import {
DataRouterContext,
DataRouterStateContext,
DataStaticRouterContext,
LocationContext,
NavigationContext,
RouteContext,
Expand Down Expand Up @@ -239,6 +238,5 @@ export {
RouteContext as UNSAFE_RouteContext,
DataRouterContext as UNSAFE_DataRouterContext,
DataRouterStateContext as UNSAFE_DataRouterStateContext,
DataStaticRouterContext as UNSAFE_DataStaticRouterContext,
enhanceManualRouteObjects as UNSAFE_enhanceManualRouteObjects,
};
49 changes: 29 additions & 20 deletions packages/react-router/lib/components.tsx
Expand Up @@ -87,27 +87,36 @@ export function RouterProvider({

let basename = router.basename || "/";

// The fragment and {null} here are important! We need them to keep React 18's
// useId happy when we are server-rendering since we may have a <script> here
// containing the hydrated server-side staticContext (from StaticRouterProvider).
// useId relies on the component tree structure to generate deterministic id's
// so we need to ensure it remains the same on the client even though
// we don't need the <script> tag
return (
<DataRouterContext.Provider
value={{
router,
navigator,
static: false,
// Do we need this?
basename,
}}
>
<DataRouterStateContext.Provider value={state}>
<Router
basename={router.basename}
location={router.state.location}
navigationType={router.state.historyAction}
navigator={navigator}
>
{router.state.initialized ? <Routes /> : fallbackElement}
</Router>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
<>
<DataRouterContext.Provider
value={{
router,
navigator,
static: false,
// Do we need this?
basename,
}}
>
<DataRouterStateContext.Provider value={state}>
<Router
basename={router.basename}
location={router.state.location}
navigationType={router.state.historyAction}
navigator={navigator}
>
{router.state.initialized ? <Routes /> : fallbackElement}
</Router>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
{null}
</>
);
}

Expand Down
8 changes: 1 addition & 7 deletions packages/react-router/lib/context.ts
Expand Up @@ -58,15 +58,9 @@ export interface RouteMatch<

export interface DataRouteMatch extends RouteMatch<string, DataRouteObject> {}

// Contexts for data routers
export const DataStaticRouterContext =
React.createContext<StaticHandlerContext | null>(null);
if (__DEV__) {
DataStaticRouterContext.displayName = "DataStaticRouterContext";
}

export interface DataRouterContextObject extends NavigationContextObject {
router: Router;
staticContext?: StaticHandlerContext;
}

export const DataRouterContext =
Expand Down
12 changes: 8 additions & 4 deletions packages/react-router/lib/hooks.tsx
Expand Up @@ -38,7 +38,6 @@ import {
RouteContext,
RouteErrorContext,
AwaitContext,
DataStaticRouterContext,
} from "./context";

/**
Expand Down Expand Up @@ -564,12 +563,17 @@ interface RenderedRouteProps {
}

function RenderedRoute({ routeContext, match, children }: RenderedRouteProps) {
let dataStaticRouterContext = React.useContext(DataStaticRouterContext);
let dataRouterContext = React.useContext(DataRouterContext);

// Track how deep we got in our render pass to emulate SSR componentDidCatch
// in a DataStaticRouter
if (dataStaticRouterContext && match.route.errorElement) {
dataStaticRouterContext._deepestRenderedBoundaryId = match.route.id;
if (
dataRouterContext &&
dataRouterContext.static &&
dataRouterContext.staticContext &&
match.route.errorElement
) {
dataRouterContext.staticContext._deepestRenderedBoundaryId = match.route.id;
}

return (
Expand Down

0 comments on commit ee2fc0c

Please sign in to comment.