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: better support for shallow route changes #1064

Merged
merged 3 commits into from
Sep 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 49 additions & 5 deletions src/appWithTranslation.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React from 'react'
import fs from 'fs'
import { screen, render } from '@testing-library/react'
import { I18nextProvider } from 'react-i18next'
import createClient from './createClient'

import { appWithTranslation } from './appWithTranslation'
import { NextRouter } from 'next/router'

jest.mock('fs', () => ({
existsSync: jest.fn(),
Expand All @@ -19,15 +21,16 @@ jest.mock('react-i18next', () => ({
__esmodule: true,
}))

jest.mock('./createClient', () => jest.fn())

const DummyApp = appWithTranslation(() => (
<div>Hello world</div>
))

const props = {
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
pageProps: {
_nextI18Next: {
initialLocale: 'en',
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
Expand All @@ -36,9 +39,15 @@ const props = {
},
},
} as any,
} as any

const renderComponent = () =>
router: {
locale: locale,
route: '/',
...router,
},
} as any)

const defaultRenderProps = createProps()
const renderComponent = (props = defaultRenderProps) =>
render(
<DummyApp
{...props}
Expand All @@ -50,6 +59,8 @@ describe('appWithTranslation', () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readdirSync as jest.Mock).mockReturnValue([]);
(I18nextProvider as jest.Mock).mockImplementation(DummyI18nextProvider)
const actualCreateClient = jest.requireActual('./createClient');
(createClient as jest.Mock).mockImplementation(actualCreateClient)
})
afterEach(jest.resetAllMocks)

Expand All @@ -69,6 +80,7 @@ describe('appWithTranslation', () => {
},
} as any)
const customProps = {
...createProps(),
pageProps: {
_nextI18Next: {
initialLocale: 'en',
Expand All @@ -91,6 +103,7 @@ describe('appWithTranslation', () => {
<div>Hello world</div>
))
const customProps = {
...createProps(),
pageProps: {
_nextI18Next: {
initialLocale: 'en',
Expand Down Expand Up @@ -124,4 +137,35 @@ describe('appWithTranslation', () => {
expect(fs.readdirSync).toHaveBeenCalledTimes(0)
})

it('should use locale from router', () => {
renderComponent(createProps('de'))
const [args] = (I18nextProvider as jest.Mock).mock.calls
expect(args[0].i18n.language).toEqual('de')
})

it('does not re-call createClient on re-renders unless locale or props have changed', () => {
const { rerender } = renderComponent()
expect(createClient).toHaveBeenCalledTimes(1)
rerender(
<DummyApp
{...defaultRenderProps}
/>
)
expect(createClient).toHaveBeenCalledTimes(1)
const newProps = createProps()
rerender(
<DummyApp
{...newProps}
/>
)
expect(createClient).toHaveBeenCalledTimes(2)
newProps.router.locale = 'de'
rerender(
<DummyApp
{...newProps}
/>
)
expect(createClient).toHaveBeenCalledTimes(3)
})

})
4 changes: 4 additions & 0 deletions src/appWithTranslation.server.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const props = {
},
},
} as any,
router: {
locale: 'en',
route: '/',
},
} as any

const renderComponent = () =>
Expand Down
32 changes: 18 additions & 14 deletions src/appWithTranslation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ export const appWithTranslation = (
configOverride: UserConfig | null = null,
) => {
const AppWithTranslation = (props: AppProps) => {
let i18n: I18NextClient | null = null
let locale = null
const { _nextI18Next } = props.pageProps
const { locale } = props.router

if (props?.pageProps?._nextI18Next) {
let { userConfig } = props.pageProps._nextI18Next
const { initialI18nStore, initialLocale } = props.pageProps._nextI18Next
// Memoize the instance and only re-initialize when either:
// 1. The route changes (non-shallowly)
// 2. Router locale changes
const i18n: I18NextClient | null = useMemo(() => {
if (!locale || !_nextI18Next) return null
isaachinman marked this conversation as resolved.
Show resolved Hide resolved

let { userConfig } = _nextI18Next
const { initialI18nStore } = _nextI18Next

if (userConfig === null && configOverride === null) {
throw new Error('appWithTranslation was called without a next-i18next config')
Expand All @@ -41,21 +46,20 @@ export const appWithTranslation = (
throw new Error('appWithTranslation was called without config.i18n')
}

locale = initialLocale;

({ i18n } = createClient({
const instance = createClient({
...createConfig({
...userConfig,
lng: initialLocale,
lng: locale,
}),
lng: initialLocale,
lng: locale,
resources: initialI18nStore,
}))
}
}).i18n

globalI18n = instance

useMemo(() => {
globalI18n = i18n
}, [i18n])
return instance
}, [_nextI18Next, locale])

return i18n !== null ? (
<I18nextProvider
Expand Down
5 changes: 3 additions & 2 deletions src/serverSideTranslations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const DummyApp = appWithTranslation(() => (
const props = {
pageProps: {
_nextI18Next: {
initialLocale: 'en',
userConfig: {
i18n: {
defaultLocale: 'en',
Expand All @@ -27,6 +26,9 @@ const props = {
},
},
} as any,
router: {
locale: 'en',
},
} as any

const renderDummyComponent = () =>
Expand Down Expand Up @@ -182,7 +184,6 @@ describe('serverSideTranslations', () => {
initialI18nStore: {
'en-US': {},
},
initialLocale: 'en-US',
userConfig: {
i18n: {
defaultLocale: 'en-US',
Expand Down
1 change: 0 additions & 1 deletion src/serverSideTranslations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ export const serverSideTranslations = async (
return {
_nextI18Next: {
initialI18nStore,
initialLocale,
userConfig: config.serializeConfig ? userConfig : null,
},
}
Expand Down
1 change: 0 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export type CreateClientReturn = {
export type SSRConfig = {
_nextI18Next: {
initialI18nStore: any
initialLocale: string
userConfig: UserConfig | null
}
}
Expand Down