-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
Ensure consistent component tree for useId #9805
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"react-router": patch | ||
"react-router-dom": patch | ||
--- | ||
|
||
Ensure useId consistency during SSR |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
||
|
@@ -98,6 +97,7 @@ export function StaticRouterProvider({ | |
router, | ||
navigator: getStatelessNavigator(), | ||
static: true, | ||
staticContext: context, | ||
basename: context.basename || "/", | ||
}; | ||
|
||
|
@@ -119,22 +119,18 @@ export function StaticRouterProvider({ | |
|
||
return ( | ||
<> | ||
<DataStaticRouterContext.Provider value={context}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This outer |
||
<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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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} | ||
</> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure the structure generated by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused why we need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mind filing a question / bug over on the react repo? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems expected - it might just be a docs issue: facebook/react#22733. Added my own comment in there suggesting a docs update |
||
); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was an |
||
export interface DataRouterContextObject extends NavigationContextObject { | ||
router: Router; | ||
staticContext?: StaticHandlerContext; | ||
} | ||
|
||
export const DataRouterContext = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,6 @@ import { | |
RouteContext, | ||
RouteErrorContext, | ||
AwaitContext, | ||
DataStaticRouterContext, | ||
} from "./context"; | ||
|
||
/** | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only reason we need |
||
} | ||
|
||
return ( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get rid of the static prop in favor of the existence of the static context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite yet - we still need
static
for non-data-routers doing SSR