Skip to content

Commit

Permalink
fix: Apply useMemo for useRouter returned from `createLocalizedPa…
Browse files Browse the repository at this point in the history
…thnamesNavigation` to keep a stable reference when possible (#1201)

Fix #1198
  • Loading branch information
amannn authored Jul 15, 2024
1 parent 98f1d2d commit a1b9a36
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 37 deletions.
2 changes: 1 addition & 1 deletion packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
},
{
"path": "dist/production/navigation.react-client.js",
"limit": "3.465 KB"
"limit": "3.475 KB"
},
{
"path": "dist/production/navigation.react-server.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
useParams,
useRouter as useNextRouter
} from 'next/navigation';
import React, {ComponentProps} from 'react';
import React, {ComponentProps, useRef} from 'react';
import {it, describe, vi, beforeEach, expect, Mock, afterEach} from 'vitest';
import {Pathnames} from '../../routing';
import createLocalizedPathnamesNavigation from './createLocalizedPathnamesNavigation';
Expand Down Expand Up @@ -229,6 +229,19 @@ describe("localePrefix: 'as-needed'", () => {
});

describe('useRouter', () => {
it('keeps a stable identity when possible', () => {
function Component() {
const router = useRouter();
const initialRouter = useRef(router);
return String(router === initialRouter.current);
}
const {rerender} = render(<Component />);
screen.getByText('true');

rerender(<Component />);
screen.getByText('true');
});

describe('push', () => {
it('resolves to the correct path when passing another locale', () => {
function Component() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {ComponentProps, ReactElement, forwardRef} from 'react';
import React, {ComponentProps, ReactElement, forwardRef, useMemo} from 'react';
import useLocale from '../../react-client/useLocale';
import {Locales, Pathnames} from '../../routing/types';
import {ParametersExceptFirst} from '../../shared/types';
Expand Down Expand Up @@ -107,49 +107,56 @@ export default function createLocalizedPathnamesNavigation<
const baseRouter = useBaseRouter(config.localePrefix);
const defaultLocale = useTypedLocale();

return {
...baseRouter,
push<Pathname extends keyof AppPathnames>(
href: HrefOrHrefWithParams<Pathname>,
...args: ParametersExceptFirst<typeof baseRouter.push>
) {
const resolvedHref = getPathname({
href,
locale: args[0]?.locale || defaultLocale
});
return baseRouter.push(resolvedHref, ...args);
},
return useMemo(
() => ({
...baseRouter,
push<Pathname extends keyof AppPathnames>(
href: HrefOrHrefWithParams<Pathname>,
...args: ParametersExceptFirst<typeof baseRouter.push>
) {
const resolvedHref = getPathname({
href,
locale: args[0]?.locale || defaultLocale
});
return baseRouter.push(resolvedHref, ...args);
},

replace<Pathname extends keyof AppPathnames>(
href: HrefOrHrefWithParams<Pathname>,
...args: ParametersExceptFirst<typeof baseRouter.replace>
) {
const resolvedHref = getPathname({
href,
locale: args[0]?.locale || defaultLocale
});
return baseRouter.replace(resolvedHref, ...args);
},
replace<Pathname extends keyof AppPathnames>(
href: HrefOrHrefWithParams<Pathname>,
...args: ParametersExceptFirst<typeof baseRouter.replace>
) {
const resolvedHref = getPathname({
href,
locale: args[0]?.locale || defaultLocale
});
return baseRouter.replace(resolvedHref, ...args);
},

prefetch<Pathname extends keyof AppPathnames>(
href: HrefOrHrefWithParams<Pathname>,
...args: ParametersExceptFirst<typeof baseRouter.prefetch>
) {
const resolvedHref = getPathname({
href,
locale: args[0]?.locale || defaultLocale
});
return baseRouter.prefetch(resolvedHref, ...args);
}
};
prefetch<Pathname extends keyof AppPathnames>(
href: HrefOrHrefWithParams<Pathname>,
...args: ParametersExceptFirst<typeof baseRouter.prefetch>
) {
const resolvedHref = getPathname({
href,
locale: args[0]?.locale || defaultLocale
});
return baseRouter.prefetch(resolvedHref, ...args);
}
}),
[baseRouter, defaultLocale]
);
}

function usePathname(): keyof AppPathnames {
const pathname = useBasePathname(config.localePrefix);
const locale = useTypedLocale();

// @ts-expect-error -- Mirror the behavior from Next.js, where `null` is returned when `usePathname` is used outside of Next, but the types indicate that a string is always returned.
return pathname ? getRoute(locale, pathname, config.pathnames) : pathname;
return useMemo(
() =>
pathname ? getRoute(locale, pathname, config.pathnames) : pathname,
[locale, pathname]
);
}

function getPathname({
Expand Down

0 comments on commit a1b9a36

Please sign in to comment.