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

@remix-run/router: Add support for navigation blocking #9709

Merged
merged 54 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
0cc6142
history: allow multiple listeners
chaance Dec 2, 2022
3c8606c
bump UMD filesize limit
chaance Dec 7, 2022
04a8c8f
feat(router): add support for history blocking APIs
chaance Dec 8, 2022
2497c02
Merge branch 'dev' into history-blocking
chaance Dec 8, 2022
cbad7dc
chore: add changeset
chaance Dec 8, 2022
5d98596
Tweak changeset
chaance Dec 8, 2022
8e0a8d0
revert unrelated history changes
chaance Dec 8, 2022
0eedecf
fix tests
chaance Dec 8, 2022
8c4ef44
add missing blockers obj
chaance Dec 8, 2022
82a8267
add missing functions for router
chaance Dec 8, 2022
226eb08
implement useBlocker
chaance Dec 9, 2022
0b24b3c
add useBlocker to example
chaance Dec 9, 2022
c5f0384
fix issues with POP blocking
chaance Dec 20, 2022
12d8860
don't recreate existing blockers
chaance Dec 21, 2022
5d04b58
add support for option
chaance Dec 21, 2022
4a5ce97
usePrompt implementation
chaance Dec 21, 2022
70c4e86
fix subtle bugs when navigating to the same route
chaance Dec 21, 2022
f2ff193
implement beforeunload handler
chaance Dec 21, 2022
6823f58
patch fix for async tests
chaance Dec 21, 2022
3545cdc
Merge branch 'dev' into history-blocking
chaance Dec 21, 2022
f56ab7d
Fix history index issue and wire up singleton blocking
brophdawg11 Dec 22, 2022
e34bde0
Merge branch 'dev' into history-blocking
chaance Dec 23, 2022
5a81d17
fix some blocking tests
chaance Jan 2, 2023
934692f
Merge branch 'dev' into history-blocking
chaance Jan 2, 2023
7285473
Add beforeUnload flag to useBlocker
brophdawg11 Jan 9, 2023
8cfc7d3
Add navigation blocking example
brophdawg11 Jan 9, 2023
d2f05a1
Updates
brophdawg11 Jan 10, 2023
12f6daa
Remove manual back button
brophdawg11 Jan 10, 2023
bb1bb0a
update router blocking tests
chaance Jan 11, 2023
50d7c80
add missing method in server
chaance Jan 11, 2023
139974d
revert changes in data router example
chaance Jan 11, 2023
e6cbdbb
update hook name in logs
chaance Jan 11, 2023
a943da9
update tests
chaance Jan 11, 2023
65bba99
add use-blocker tests
chaance Jan 13, 2023
23d3aac
Merge branch 'history-blocking' of github.com:remix-run/react-router …
chaance Jan 13, 2023
2859b13
Merge branch 'dev' into history-blocking
chaance Jan 13, 2023
ba5c76d
update tests
chaance Jan 13, 2023
6fba7e5
bump umd bundle size limit
chaance Jan 13, 2023
f4d6b00
update blocker and shouldBlock function interface
chaance Jan 13, 2023
e25325e
update example
chaance Jan 13, 2023
27568f7
update example and mark exports as unstable
chaance Jan 13, 2023
fa00b37
add changeset
chaance Jan 13, 2023
546e3ac
update changeset
chaance Jan 13, 2023
76e7fde
update readme
chaance Jan 13, 2023
2334a5b
relax reliance on boolean type for blocker function
chaance Jan 13, 2023
b507993
update tests
chaance Jan 13, 2023
e1426d0
Revert to prior usePrompt example
brophdawg11 Jan 13, 2023
16e464f
Move useBlocker to react-router, remove getBlockerState
brophdawg11 Jan 13, 2023
b9739ba
Minor updates
brophdawg11 Jan 13, 2023
28da8cb
update build script
chaance Jan 13, 2023
b29b5e0
Bump bundle
brophdawg11 Jan 13, 2023
887314a
export from react-router-native
chaance Jan 13, 2023
6f15186
Merge branch 'history-blocking' of github.com:remix-run/react-router …
chaance Jan 13, 2023
7749172
rm prompt from example
chaance Jan 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions examples/navigation-blocking/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,6 @@ function ImportantFormWithBlocker() {
}
}, [blocker, isBlocked]);

// Display our confirmation UI
const blockerUI: Record<Blocker["state"], React.ReactElement> = {
unblocked: <p style={{ color: "green" }}>Blocker is currently unblocked</p>,
blocked: (
<>
<p style={{ color: "red" }}>Blocked the last navigation</p>
<button onClick={() => blocker.proceed?.()}>Let me through</button>
<button onClick={() => blocker.reset?.()}>Keep me here</button>
</>
),
proceeding: (
<p style={{ color: "orange" }}>Proceeding through blocked navigation</p>
),
};

return (
<>
<p>
Expand All @@ -190,11 +175,33 @@ function ImportantFormWithBlocker() {
<button type="submit">Save</button>
</Form>

{blockerUI[blocker.state]}
{blocker ? <ConfirmNavigation blocker={blocker} /> : null}
</>
);
}

function ConfirmNavigation({ blocker }: { blocker: Blocker }) {
if (blocker.state === "blocked") {
return (
<>
<p style={{ color: "red" }}>
Blocked the last navigation to {blocker.location.pathname}
Copy link
Contributor

Choose a reason for hiding this comment

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

Moved this to a separate component so this wouldn't blow up on blocker.location.pathname when in an unblocked state. everything got evaluated in the above key lookup approach.

</p>
<button onClick={() => blocker.proceed?.()}>Let me through</button>
<button onClick={() => blocker.reset?.()}>Keep me here</button>
</>
);
}

if (blocker.state === "proceeding") {
return (
<p style={{ color: "orange" }}>Proceeding through blocked navigation</p>
);
}

return <p style={{ color: "green" }}>Blocker is currently unblocked</p>;
}

function ImportantFormWithPrompt() {
let [value, setValue] = React.useState("");
let isBlocked = value !== "";
Expand Down
3 changes: 3 additions & 0 deletions packages/react-router-native/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export type {
ActionFunction,
ActionFunctionArgs,
AwaitProps,
unstable_Blocker,
unstable_BlockerFunction,
DataRouteMatch,
DataRouteObject,
Fetcher,
Expand Down Expand Up @@ -89,6 +91,7 @@ export {
useActionData,
useAsyncError,
useAsyncValue,
unstable_useBlocker,
useHref,
useInRouterContext,
useLoaderData,
Expand Down
5 changes: 4 additions & 1 deletion packages/router/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ export interface Update {
*/
location: Location;

/**
* The delta between this location and the former location in the history stack
*/
delta: number;
}

Expand Down Expand Up @@ -615,8 +618,8 @@ function getUrlBasedHistory(
if (nextIndex != null) {
let delta = nextIndex - index;
action = nextAction;
index = nextIndex;
if (listener) {
index = nextIndex;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should always be updating this even if no one is listening - never showed up as an issue since we always have a listener

listener({ action, location: history.location, delta });
}
} else {
Expand Down
12 changes: 2 additions & 10 deletions packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,15 +1112,6 @@ export function createRouter(init: RouterInit): Router {
return;
}

// Short circuit if navigation is blocked
if (
Array.from(state.blockers).some(([_, blocker]) => {
return blocker.state === "blocked";
})
) {
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I think his is leftover form very early. Our entry points are popstate and router.navigate so we can just check for blocking there

// Short circuit if it's only a hash change
if (isHashChangeOnly(state.location, location)) {
completeNavigation(location, { matches });
Expand Down Expand Up @@ -2127,6 +2118,8 @@ export function createRouter(init: RouterInit): Router {
function updateBlocker(key: string, newBlocker: Blocker) {
let blocker = state.blockers.get(key) || IDLE_BLOCKER;

// Poor mans state machine :)
// https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
invariant(
(blocker.state === "unblocked" && newBlocker.state === "blocked") ||
(blocker.state === "blocked" && newBlocker.state === "blocked") ||
Expand Down Expand Up @@ -3673,5 +3666,4 @@ function getTargetMatch(
let pathMatches = getPathContributingMatches(matches);
return pathMatches[pathMatches.length - 1];
}

//#endregion