Skip to content

Commit 0823407

Browse files
appdenbrophdawg11
andauthored
fix(react-router): improve memoization for context providers (#9983)
Co-authored-by: Matt Brophy <[email protected]>
1 parent c4fa61a commit 0823407

File tree

4 files changed

+39
-25
lines changed

4 files changed

+39
-25
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Improve memoization for context providers to avoid unnecessary re-renders

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- AmRo045
1313
- amsal
1414
- andreiduca
15+
- appden
1516
- arnassavickas
1617
- aroyan
1718
- avipatel97

packages/react-router/lib/components.tsx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,16 @@ export function RouterProvider({
5656
fallbackElement,
5757
router,
5858
}: RouterProviderProps): React.ReactElement {
59+
let getState = React.useCallback(() => router.state, [router]);
60+
5961
// Sync router state to our component state to force re-renders
6062
let state: RouterState = useSyncExternalStoreShim(
6163
router.subscribe,
62-
() => router.state,
64+
getState,
6365
// We have to provide this so React@18 doesn't complain during hydration,
6466
// but we pass our serialized hydration data into the router so state here
6567
// is already synced with what the server saw
66-
() => router.state
68+
getState
6769
);
6870

6971
let navigator = React.useMemo((): Navigator => {
@@ -87,6 +89,16 @@ export function RouterProvider({
8789

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

92+
let dataRouterContext = React.useMemo(
93+
() => ({
94+
router,
95+
navigator,
96+
static: false,
97+
basename,
98+
}),
99+
[router, navigator, basename]
100+
);
101+
90102
// The fragment and {null} here are important! We need them to keep React 18's
91103
// useId happy when we are server-rendering since we may have a <script> here
92104
// containing the hydrated server-side staticContext (from StaticRouterProvider).
@@ -95,15 +107,7 @@ export function RouterProvider({
95107
// we don't need the <script> tag
96108
return (
97109
<>
98-
<DataRouterContext.Provider
99-
value={{
100-
router,
101-
navigator,
102-
static: false,
103-
// Do we need this?
104-
basename,
105-
}}
106-
>
110+
<DataRouterContext.Provider value={dataRouterContext}>
107111
<DataRouterStateContext.Provider value={state}>
108112
<Router
109113
basename={router.basename}
@@ -330,39 +334,39 @@ export function Router({
330334
key = "default",
331335
} = locationProp;
332336

333-
let location = React.useMemo(() => {
337+
let locationContext = React.useMemo(() => {
334338
let trailingPathname = stripBasename(pathname, basename);
335339

336340
if (trailingPathname == null) {
337341
return null;
338342
}
339343

340344
return {
341-
pathname: trailingPathname,
342-
search,
343-
hash,
344-
state,
345-
key,
345+
location: {
346+
pathname: trailingPathname,
347+
search,
348+
hash,
349+
state,
350+
key,
351+
},
352+
navigationType,
346353
};
347-
}, [basename, pathname, search, hash, state, key]);
354+
}, [basename, pathname, search, hash, state, key, navigationType]);
348355

349356
warning(
350-
location != null,
357+
locationContext != null,
351358
`<Router basename="${basename}"> is not able to match the URL ` +
352359
`"${pathname}${search}${hash}" because it does not start with the ` +
353360
`basename, so the <Router> won't render anything.`
354361
);
355362

356-
if (location == null) {
363+
if (locationContext == null) {
357364
return null;
358365
}
359366

360367
return (
361368
<NavigationContext.Provider value={navigationContext}>
362-
<LocationContext.Provider
363-
children={children}
364-
value={{ location, navigationType }}
365-
/>
369+
<LocationContext.Provider children={children} value={locationContext} />
366370
</NavigationContext.Provider>
367371
);
368372
}

packages/react-router/lib/hooks.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ enum DataRouterHook {
667667
}
668668

669669
enum DataRouterStateHook {
670+
UseBlocker = "useBlocker",
670671
UseLoaderData = "useLoaderData",
671672
UseActionData = "useActionData",
672673
UseRouteError = "useRouteError",
@@ -841,6 +842,7 @@ let blockerId = 0;
841842
*/
842843
export function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker {
843844
let { router } = useDataRouterContext(DataRouterHook.UseBlocker);
845+
let state = useDataRouterState(DataRouterStateHook.UseBlocker);
844846
let [blockerKey] = React.useState(() => String(++blockerId));
845847

846848
let blockerFunction = React.useCallback<BlockerFunction>(
@@ -860,7 +862,9 @@ export function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker {
860862
[router, blockerKey]
861863
);
862864

863-
return blocker;
865+
// Prefer the blocker from state since DataRouterContext is memoized so this
866+
// ensures we update on blocker state updates
867+
return state.blockers.get(blockerKey) || blocker;
864868
}
865869

866870
const alreadyWarned: Record<string, boolean> = {};

0 commit comments

Comments
 (0)