Skip to content

Commit ee2fc0c

Browse files
authored
Ensure consistent component tree for useId (#9805)
1 parent b5d10b9 commit ee2fc0c

File tree

8 files changed

+56
-51
lines changed

8 files changed

+56
-51
lines changed

.changeset/three-ladybugs-clap.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"react-router": patch
3+
"react-router-dom": patch
4+
---
5+
6+
Ensure useId consistency during SSR

packages/react-router-dom/index.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ export {
178178
export {
179179
UNSAFE_DataRouterContext,
180180
UNSAFE_DataRouterStateContext,
181-
UNSAFE_DataStaticRouterContext,
182181
UNSAFE_NavigationContext,
183182
UNSAFE_LocationContext,
184183
UNSAFE_RouteContext,

packages/react-router-dom/server.tsx

+12-16
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
Router,
2828
UNSAFE_DataRouterContext as DataRouterContext,
2929
UNSAFE_DataRouterStateContext as DataRouterStateContext,
30-
UNSAFE_DataStaticRouterContext as DataStaticRouterContext,
3130
UNSAFE_enhanceManualRouteObjects as enhanceManualRouteObjects,
3231
} from "react-router-dom";
3332

@@ -98,6 +97,7 @@ export function StaticRouterProvider({
9897
router,
9998
navigator: getStatelessNavigator(),
10099
static: true,
100+
staticContext: context,
101101
basename: context.basename || "/",
102102
};
103103

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

120120
return (
121121
<>
122-
<DataStaticRouterContext.Provider value={context}>
123-
<DataRouterContext.Provider value={dataRouterContext}>
124-
<DataRouterStateContext.Provider
125-
value={dataRouterContext.router.state}
122+
<DataRouterContext.Provider value={dataRouterContext}>
123+
<DataRouterStateContext.Provider value={dataRouterContext.router.state}>
124+
<Router
125+
basename={dataRouterContext.basename}
126+
location={dataRouterContext.router.state.location}
127+
navigationType={dataRouterContext.router.state.historyAction}
128+
navigator={dataRouterContext.navigator}
126129
>
127-
<Router
128-
basename={dataRouterContext.basename}
129-
location={dataRouterContext.router.state.location}
130-
navigationType={dataRouterContext.router.state.historyAction}
131-
navigator={dataRouterContext.navigator}
132-
>
133-
<Routes />
134-
</Router>
135-
</DataRouterStateContext.Provider>
136-
</DataRouterContext.Provider>
137-
</DataStaticRouterContext.Provider>
130+
<Routes />
131+
</Router>
132+
</DataRouterStateContext.Provider>
133+
</DataRouterContext.Provider>
138134
{hydrateScript ? (
139135
<script
140136
suppressHydrationWarning

packages/react-router-native/index.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ export {
125125
export {
126126
UNSAFE_DataRouterContext,
127127
UNSAFE_DataRouterStateContext,
128-
UNSAFE_DataStaticRouterContext,
129128
UNSAFE_NavigationContext,
130129
UNSAFE_LocationContext,
131130
UNSAFE_RouteContext,

packages/react-router/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ import type {
7676
import {
7777
DataRouterContext,
7878
DataRouterStateContext,
79-
DataStaticRouterContext,
8079
LocationContext,
8180
NavigationContext,
8281
RouteContext,
@@ -239,6 +238,5 @@ export {
239238
RouteContext as UNSAFE_RouteContext,
240239
DataRouterContext as UNSAFE_DataRouterContext,
241240
DataRouterStateContext as UNSAFE_DataRouterStateContext,
242-
DataStaticRouterContext as UNSAFE_DataStaticRouterContext,
243241
enhanceManualRouteObjects as UNSAFE_enhanceManualRouteObjects,
244242
};

packages/react-router/lib/components.tsx

+29-20
Original file line numberDiff line numberDiff line change
@@ -87,27 +87,36 @@ export function RouterProvider({
8787

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

90+
// The fragment and {null} here are important! We need them to keep React 18's
91+
// useId happy when we are server-rendering since we may have a <script> here
92+
// containing the hydrated server-side staticContext (from StaticRouterProvider).
93+
// useId relies on the component tree structure to generate deterministic id's
94+
// so we need to ensure it remains the same on the client even though
95+
// we don't need the <script> tag
9096
return (
91-
<DataRouterContext.Provider
92-
value={{
93-
router,
94-
navigator,
95-
static: false,
96-
// Do we need this?
97-
basename,
98-
}}
99-
>
100-
<DataRouterStateContext.Provider value={state}>
101-
<Router
102-
basename={router.basename}
103-
location={router.state.location}
104-
navigationType={router.state.historyAction}
105-
navigator={navigator}
106-
>
107-
{router.state.initialized ? <Routes /> : fallbackElement}
108-
</Router>
109-
</DataRouterStateContext.Provider>
110-
</DataRouterContext.Provider>
97+
<>
98+
<DataRouterContext.Provider
99+
value={{
100+
router,
101+
navigator,
102+
static: false,
103+
// Do we need this?
104+
basename,
105+
}}
106+
>
107+
<DataRouterStateContext.Provider value={state}>
108+
<Router
109+
basename={router.basename}
110+
location={router.state.location}
111+
navigationType={router.state.historyAction}
112+
navigator={navigator}
113+
>
114+
{router.state.initialized ? <Routes /> : fallbackElement}
115+
</Router>
116+
</DataRouterStateContext.Provider>
117+
</DataRouterContext.Provider>
118+
{null}
119+
</>
111120
);
112121
}
113122

packages/react-router/lib/context.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,9 @@ export interface RouteMatch<
5858

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

61-
// Contexts for data routers
62-
export const DataStaticRouterContext =
63-
React.createContext<StaticHandlerContext | null>(null);
64-
if (__DEV__) {
65-
DataStaticRouterContext.displayName = "DataStaticRouterContext";
66-
}
67-
6861
export interface DataRouterContextObject extends NavigationContextObject {
6962
router: Router;
63+
staticContext?: StaticHandlerContext;
7064
}
7165

7266
export const DataRouterContext =

packages/react-router/lib/hooks.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import {
3838
RouteContext,
3939
RouteErrorContext,
4040
AwaitContext,
41-
DataStaticRouterContext,
4241
} from "./context";
4342

4443
/**
@@ -564,12 +563,17 @@ interface RenderedRouteProps {
564563
}
565564

566565
function RenderedRoute({ routeContext, match, children }: RenderedRouteProps) {
567-
let dataStaticRouterContext = React.useContext(DataStaticRouterContext);
566+
let dataRouterContext = React.useContext(DataRouterContext);
568567

569568
// Track how deep we got in our render pass to emulate SSR componentDidCatch
570569
// in a DataStaticRouter
571-
if (dataStaticRouterContext && match.route.errorElement) {
572-
dataStaticRouterContext._deepestRenderedBoundaryId = match.route.id;
570+
if (
571+
dataRouterContext &&
572+
dataRouterContext.static &&
573+
dataRouterContext.staticContext &&
574+
match.route.errorElement
575+
) {
576+
dataRouterContext.staticContext._deepestRenderedBoundaryId = match.route.id;
573577
}
574578

575579
return (

0 commit comments

Comments
 (0)