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

Fix unstable_useBlocker key issues in strict mode #10573

Merged
merged 8 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/blocker-key-strict-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"react-router": patch
"@remix-run/router": patch
---

Fix `unstable_useBlocker` key issues in `StrictMode`
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@
"none": "45 kB"
},
"packages/react-router/dist/react-router.production.min.js": {
"none": "13.4 kB"
"none": "13.5 kB"
},
"packages/react-router/dist/umd/react-router.production.min.js": {
"none": "15.8 kB"
"none": "15.9 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
"none": "12.1 kB"
Expand Down
23 changes: 12 additions & 11 deletions packages/react-router/lib/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
matchRoutes,
parsePath,
resolveTo,
IDLE_BLOCKER,
UNSAFE_getPathContributingMatches as getPathContributingMatches,
UNSAFE_warning as warning,
} from "@remix-run/router";
Expand Down Expand Up @@ -935,8 +936,9 @@ let blockerId = 0;
export function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker {
let { router } = useDataRouterContext(DataRouterHook.UseBlocker);
let state = useDataRouterState(DataRouterStateHook.UseBlocker);
let [blockerKey] = React.useState(() => String(++blockerId));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved this increment into a side effect to be compliant with StrictMode. Fetchers probably have a similar issue but they don't need to put anything like a blocker function into an internal router cache so it doesn't end up causing any user-facing issues.


let [blockerKey, setBlockerKey] = React.useState("");
let [blocker, setBlocker] = React.useState<Blocker>(IDLE_BLOCKER);
let blockerFunction = React.useCallback<BlockerFunction>(
(args) => {
return typeof shouldBlock === "function"
Expand All @@ -946,17 +948,16 @@ export function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker {
[shouldBlock]
);

let blocker = router.getBlocker(blockerKey, blockerFunction);
React.useEffect(() => {

Choose a reason for hiding this comment

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

I believe this introduced a useEffect loop since setBlockerKey is used in the dependency array and changed in the same effect.

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

#10650 please have a look

let key = String(++blockerId);
setBlocker(router.getBlocker(key, blockerFunction));
setBlockerKey(key);
return () => router.deleteBlocker(key);
}, [router, setBlocker, setBlockerKey, blockerFunction]);

// Cleanup on unmount
React.useEffect(
() => () => router.deleteBlocker(blockerKey),
[router, blockerKey]
);

// Prefer the blocker from state since DataRouterContext is memoized so this
// ensures we update on blocker state updates
return state.blockers.get(blockerKey) || blocker;
return blockerKey && state.blockers.has(blockerKey)
? state.blockers.get(blockerKey)!
: blocker;
}

/**
Expand Down
22 changes: 12 additions & 10 deletions packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,9 @@ export function createRouter(init: RouterInit): Router {
init.history.go(delta);
},
reset() {
deleteBlocker(blockerKey!);
updateState({ blockers: new Map(router.state.blockers) });
let blockers = new Map(state.blockers);
blockers.set(blockerKey!, IDLE_BLOCKER);
updateState({ blockers });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Avoid mutating the state Map directly

},
});
return;
Expand Down Expand Up @@ -991,9 +992,7 @@ export function createRouter(init: RouterInit): Router {

// On a successful navigation we can assume we got through all blockers
// so we can start fresh
for (let [key] of blockerFunctions) {
deleteBlocker(key);
}
blockerFunctions.clear();

// Always respect the user flag. Otherwise don't reset on mutation
// submission navigations unless they redirect
Expand Down Expand Up @@ -1032,7 +1031,7 @@ export function createRouter(init: RouterInit): Router {
newState.matches || state.matches
),
preventScrollReset,
blockers: new Map(state.blockers),
blockers: new Map(),
});

// Reset stateful navigation vars
Expand Down Expand Up @@ -1130,8 +1129,10 @@ export function createRouter(init: RouterInit): Router {
navigate(to, opts);
},
reset() {
deleteBlocker(blockerKey!);
updateState({ blockers: new Map(state.blockers) });
// TODO: Crate new map before mutating - same for fetchers if possible
let blockers = new Map(state.blockers);
blockers.set(blockerKey!, IDLE_BLOCKER);
updateState({ blockers });
},
});
return;
Expand Down Expand Up @@ -2378,8 +2379,9 @@ export function createRouter(init: RouterInit): Router {
`Invalid blocker state transition: ${blocker.state} -> ${newBlocker.state}`
);

state.blockers.set(key, newBlocker);
updateState({ blockers: new Map(state.blockers) });
let blockers = new Map(state.blockers);
blockers.set(key, newBlocker);
updateState({ blockers });
}

function shouldBlockNavigation({
Expand Down