Skip to content

Commit 9bbc924

Browse files
authored
Merge pull request #46 from qonto/use-navigate-provider-hook
feat(useNavigate): add hook and its provider
2 parents 46a6d80 + 85e387a commit 9bbc924

File tree

8 files changed

+156
-10
lines changed

8 files changed

+156
-10
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { LDProvider, type LDFlagSet } from './launchdarkly-context';
22
export { PolymorphicRouterContextProvider } from './polymorphic-router-context';
3+
export { PolymorphicNavigateProvider } from './polymorphic-navigate-context';
34
export { ApplicationProvider } from './application-context';
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { ReactNode } from 'react';
2+
import { createContext, useContext } from 'react';
3+
4+
export interface PolymorphicNavigate {
5+
(
6+
to: string | Path,
7+
options?: NavigateOptions,
8+
): void | Promise<void> | Promise<boolean>;
9+
}
10+
11+
interface Path
12+
extends Partial<{
13+
hash: string;
14+
pathname: string;
15+
search: string;
16+
}> {
17+
pathname: string;
18+
}
19+
20+
export type RelativeRoutingType = 'route' | 'path';
21+
22+
export interface NavigateOptions {
23+
flushSync?: boolean;
24+
preventScrollReset?: boolean;
25+
relative?: RelativeRoutingType;
26+
replace?: boolean;
27+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This type comes from react-router
28+
state?: any;
29+
viewTransition?: boolean;
30+
}
31+
32+
interface NavigateContextProviderProps {
33+
children: ReactNode;
34+
navigate: PolymorphicNavigate;
35+
}
36+
37+
export const PolymorphicNavigateContext =
38+
createContext<PolymorphicNavigate | null>(null);
39+
40+
export function PolymorphicNavigateProvider({
41+
children,
42+
navigate,
43+
}: NavigateContextProviderProps): ReactNode {
44+
return (
45+
<PolymorphicNavigateContext.Provider value={navigate}>
46+
{children}
47+
</PolymorphicNavigateContext.Provider>
48+
);
49+
}
50+
51+
export function useNavigate(): PolymorphicNavigate {
52+
const navigate = useContext(PolymorphicNavigateContext);
53+
54+
if (!navigate)
55+
throw new Error(
56+
'this hook can only be used with PolymorphicNavigateProvider',
57+
);
58+
59+
return navigate;
60+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { useEmberService } from './use-ember-service';
22
export { useApplicationInstance } from './use-application-instance';
33
export { useFlags } from './use-flags';
4+
export { useNavigate } from './use-navigate';
45
export { useRouter } from './use-router';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { useEmberIntl } from './use-ember-intl';
22
export { useEmberRouter } from './use-ember-router';
3+
export { useNavigate } from './use-navigate';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useCallback } from 'react';
2+
import type { RouteModel } from '@ember/routing/router-service';
3+
import type Transition from '@ember/routing/transition';
4+
import { useEmberService } from '../../hooks';
5+
import { parseUrl } from '../../utils/url';
6+
import type { UrlObject } from '../../types/router';
7+
8+
interface PolymorphicNavigate {
9+
(
10+
to: string | Path,
11+
options?: NavigateOptions,
12+
): void | Promise<void> | Promise<boolean>;
13+
}
14+
15+
interface Path
16+
extends Partial<{
17+
hash: string;
18+
pathname: string;
19+
search: string;
20+
}> {
21+
pathname: string;
22+
}
23+
24+
type RelativeRoutingType = 'route' | 'path';
25+
26+
interface NavigateOptions {
27+
flushSync?: boolean;
28+
preventScrollReset?: boolean;
29+
relative?: RelativeRoutingType;
30+
replace?: boolean;
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- this comes from react router
32+
state?: any;
33+
viewTransition?: boolean;
34+
}
35+
36+
export const useNavigate = (): PolymorphicNavigate => {
37+
const emberRouter = useEmberService('router');
38+
39+
return useCallback(
40+
(
41+
url: string | UrlObject,
42+
options: NavigateOptions = {},
43+
): Promise<boolean> => {
44+
const parsedUrl = parseUrl(url);
45+
const { queryParams, params } = emberRouter.recognize(parsedUrl);
46+
const models = Object.values(params) as RouteModel[];
47+
let transition: Transition<boolean>;
48+
if (options.replace) {
49+
transition = emberRouter.replaceWith(parsedUrl, models, {
50+
queryParams,
51+
}) as Transition<boolean>;
52+
} else {
53+
transition = emberRouter.transitionTo(parsedUrl, models, {
54+
queryParams,
55+
}) as Transition<boolean>;
56+
}
57+
return transition.promise;
58+
},
59+
[emberRouter],
60+
);
61+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useContext } from 'react';
2+
import {
3+
PolymorphicNavigateContext,
4+
type PolymorphicNavigate,
5+
} from '../contexts/polymorphic-navigate-context';
6+
7+
export const useNavigate = (): PolymorphicNavigate => {
8+
const navigate = useContext(PolymorphicNavigateContext);
9+
if (!navigate) {
10+
throw new Error(
11+
'this hook can only be used with PolymorphicNavigateProvider',
12+
);
13+
}
14+
return navigate;
15+
};
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import type { PropsWithChildren, ReactNode } from "react";
22
import { ThemeProvider } from "./theme-context.tsx";
3-
import { PolymorphicRouterContextProvider } from "@qonto/react-migration-toolkit/react/providers";
3+
import {
4+
PolymorphicNavigateProvider,
5+
PolymorphicRouterContextProvider,
6+
} from "@qonto/react-migration-toolkit/react/providers";
47
import {
58
useEmberIntl,
69
useEmberRouter,
10+
useNavigate,
711
} from "@qonto/react-migration-toolkit/react/hooks/providers";
812
import { RawIntlProvider } from "react-intl";
913

1014
export function CustomProviders({ children }: PropsWithChildren): ReactNode {
1115
const intl = useEmberIntl();
1216
const router = useEmberRouter();
17+
const navigate = useNavigate();
1318

1419
return (
1520
<ThemeProvider theme={{ current: "light" }} data-test-providers>
16-
<PolymorphicRouterContextProvider router={router}>
17-
<RawIntlProvider value={intl}>{children}</RawIntlProvider>
18-
</PolymorphicRouterContextProvider>
21+
<PolymorphicNavigateProvider navigate={navigate}>
22+
<PolymorphicRouterContextProvider router={router}>
23+
<RawIntlProvider value={intl}>{children}</RawIntlProvider>
24+
</PolymorphicRouterContextProvider>
25+
</PolymorphicNavigateProvider>
1926
</ThemeProvider>
2027
);
2128
}

test-app/app/react/example-routing.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Link } from "@qonto/react-migration-toolkit/react/components";
2-
import { useRouter } from "@qonto/react-migration-toolkit/react/hooks";
2+
import { useNavigate } from "@qonto/react-migration-toolkit/react/hooks";
33

44
export function ExampleRouting() {
5-
const router = useRouter();
5+
const navigate = useNavigate();
66

77
return (
88
<div>
@@ -17,7 +17,7 @@ export function ExampleRouting() {
1717
<button
1818
data-test-about-button-push
1919
onClick={() => {
20-
router.push("/about");
20+
navigate("/about");
2121
}}
2222
>
2323
Push to About page
@@ -26,7 +26,7 @@ export function ExampleRouting() {
2626
<button
2727
data-test-about-button-push-query
2828
onClick={() => {
29-
router.push("/about?foo=bar&baz=qux");
29+
navigate("/about?foo=bar&baz=qux");
3030
}}
3131
>
3232
Push to About page with query params
@@ -35,7 +35,7 @@ export function ExampleRouting() {
3535
<button
3636
data-test-about-button-replace
3737
onClick={() => {
38-
router.replace("/about");
38+
navigate("/about", { replace: true });
3939
}}
4040
>
4141
Replace to About page
@@ -44,7 +44,7 @@ export function ExampleRouting() {
4444
<button
4545
data-test-about-button-replace-query
4646
onClick={() => {
47-
router.replace("/about?foo=bar&baz=qux");
47+
navigate("/about?foo=bar&baz=qux", { replace: true });
4848
}}
4949
>
5050
Replace to About page with query params

0 commit comments

Comments
 (0)