Skip to content

Commit

Permalink
refactor: reuse existing instance (#2226)
Browse files Browse the repository at this point in the history
* refactor: reuse existing instance

* fix: change language for existing instance

* fix: state update in render error

* fix: move addResourceBundle into useMemo
  • Loading branch information
wjaykim committed Nov 1, 2023
1 parent da90fae commit 23f2139
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 18 deletions.
6 changes: 3 additions & 3 deletions src/appWithTranslation.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,18 +277,18 @@ describe('appWithTranslation', () => {
expect(args[0].i18n.language).toBe('de')
})

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

it('assures locale key is set to the right value', () => {
Expand Down
64 changes: 49 additions & 15 deletions src/appWithTranslation.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useMemo, useRef } from 'react'
import hoistNonReactStatics from 'hoist-non-react-statics'
import { I18nextProvider } from 'react-i18next'
import type { AppProps as NextJsAppProps } from 'next/app'
Expand All @@ -9,6 +9,7 @@ import createClient from './createClient'
import { SSRConfig, UserConfig } from './types'

import { i18n as I18NextClient } from 'i18next'
import { useIsomorphicLayoutEffect } from './utils'
export {
Trans,
useTranslation,
Expand All @@ -29,10 +30,13 @@ export const appWithTranslation = <Props extends NextJsAppProps>(
_nextI18Next?.initialLocale ?? props?.router?.locale
const ns = _nextI18Next?.ns

// Memoize the instance and only re-initialize when either:
// 1. The route changes (non-shallowly)
// 2. Router locale changes
// 3. UserConfig override changes
const instanceRef = useRef<I18NextClient | null>(null)

/**
* Memoize i18n instance and reuse it rather than creating new instance.
* When the locale or resources are changed after instance was created,
* we will update the instance by calling addResourceBundle method on it.
*/
const i18n: I18NextClient | null = useMemo(() => {
if (!_nextI18Next && !configOverride) return null

Expand Down Expand Up @@ -63,20 +67,50 @@ export const appWithTranslation = <Props extends NextJsAppProps>(

if (!locale) locale = userConfig.i18n.defaultLocale

const instance = createClient({
...createConfig({
...userConfig,
let instance = instanceRef.current
if (instance) {
if (resources) {
for (const locale of Object.keys(resources)) {
for (const ns of Object.keys(resources[locale])) {
instance.addResourceBundle(
locale,
ns,
resources[locale][ns],
true,
true
)
}
}
}
} else {
instance = createClient({
...createConfig({
...userConfig,
lng: locale,
}),
lng: locale,
}),
lng: locale,
ns,
resources,
}).i18n
ns,
resources,
}).i18n

globalI18n = instance
globalI18n = instance
instanceRef.current = instance
}

return instance
}, [_nextI18Next, locale, configOverride, ns])
}, [_nextI18Next, locale, ns])

/**
* Since calling changeLanguage method on existing i18n instance cause state update in react,
* we need to call the method in `useLayoutEffect` to prevent state update in render phase.
*/
useIsomorphicLayoutEffect(() => {
if (!i18n || !locale) {
return
}

i18n.changeLanguage(locale)
}, [i18n, locale])

return i18n !== null ? (
<I18nextProvider i18n={i18n}>
Expand Down
11 changes: 11 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FallbackLng, FallbackLngObjList } from 'i18next'
import { useLayoutEffect, useEffect } from 'react'

export const getFallbackForLng = (
lng: string,
Expand Down Expand Up @@ -28,3 +29,13 @@ export const getFallbackForLng = (

export const unique = (list: string[]) =>
Array.from(new Set<string>(list))

/**
* This hook behaves like `useLayoutEffect` on the client,
* and `useEffect` on the server(no effect).
*
* Since using `useLayoutEffect` on the server cause warning messages in nextjs,
* this hook is workaround for that.
*/
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect

0 comments on commit 23f2139

Please sign in to comment.