Skip to content

Commit

Permalink
fix: state update in render error
Browse files Browse the repository at this point in the history
  • Loading branch information
김우재 committed Nov 1, 2023
1 parent 6276e99 commit fcceef0
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 59 deletions.
139 changes: 80 additions & 59 deletions src/appWithTranslation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -31,73 +32,93 @@ export const appWithTranslation = <Props extends NextJsAppProps>(

const instanceRef = useRef<I18NextClient | null>(null)

// Memoize the instance and only re-initialize when either:
// 1. The route changes (non-shallowly)
// 2. Router locale changes
// 3. UserConfig override changes
const i18n: I18NextClient | null = useMemo(() => {
if (!_nextI18Next && !configOverride) return 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 in `useLayoutEffect` below.
*/
const [i18n, updateData]: [I18NextClient | null, any?] =
useMemo(() => {
if (!_nextI18Next && !configOverride) return [null]

const userConfig = configOverride ?? _nextI18Next?.userConfig

if (!userConfig) {
throw new Error(
'appWithTranslation was called without a next-i18next config'
)
}

const userConfig = configOverride ?? _nextI18Next?.userConfig
if (!userConfig?.i18n) {
throw new Error(
'appWithTranslation was called without config.i18n'
)
}

if (!userConfig) {
throw new Error(
'appWithTranslation was called without a next-i18next config'
)
}
if (!userConfig?.i18n?.defaultLocale) {
throw new Error(
'config.i18n does not include a defaultLocale property'
)
}

if (!userConfig?.i18n) {
throw new Error(
'appWithTranslation was called without config.i18n'
)
}
const { initialI18nStore } = _nextI18Next || {}
const resources = configOverride?.resources
? configOverride.resources
: initialI18nStore

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

let instance = instanceRef.current
if (instance) {
return [
instance,
{
locale,
resources,
},
]
} else {
instance = createClient({
...createConfig({
...userConfig,
lng: locale,
}),
lng: locale,
ns,
resources,
}).i18n

if (!userConfig?.i18n?.defaultLocale) {
throw new Error(
'config.i18n does not include a defaultLocale property'
)
globalI18n = instance
instanceRef.current = instance
return [instance]
}
}, [_nextI18Next, locale, ns])

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

const { initialI18nStore } = _nextI18Next || {}
const resources = configOverride?.resources
? configOverride.resources
: initialI18nStore

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

let instance = instanceRef.current
if (instance) {
instance.changeLanguage(locale)
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
)
}
const { locale, resources } = updateData

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

globalI18n = instance
instanceRef.current = instance
}

return instance
}, [_nextI18Next, locale, ns])
}, [i18n, updateData])

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 fcceef0

Please sign in to comment.