fix(ui): stale locale value from useLocale (#9582)
### What? Fixes issue with stale locale from searchParams ### Why? Bad use of useEffect/useState inside our useSearchParams provider. ### How? Memoize the locale instead of relying on the useEffect which was causing unnecessary renders with stale values.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { withPayload } from "@payloadcms/next/withPayload";
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "payload-3-custom-server",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "next build && tsc --project tsconfig.server.json",
|
||||
"start": "cross-env NODE_ENV=production node dist/server.js",
|
||||
"dev": "nodemon",
|
||||
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
|
||||
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload"
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"start": "cross-env NODE_ENV=production node dist/server.js"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/next": "latest",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function B() {
|
||||
return <div>b</div>;
|
||||
return <div>b</div>
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
|
||||
|
||||
export const importMap = {
|
||||
|
||||
}
|
||||
export const importMap = {}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
const { submit } = useForm()
|
||||
const modified = useFormModified()
|
||||
const editDepth = useEditDepth()
|
||||
const { code: locale } = useLocale()
|
||||
const { code: localeCode } = useLocale()
|
||||
|
||||
const {
|
||||
localization,
|
||||
@@ -40,7 +40,6 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
} = config
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
const { code } = useLocale()
|
||||
const label = labelProp || t('version:publishChanges')
|
||||
|
||||
const hasNewerVersions = unpublishedVersionCount > 0
|
||||
@@ -54,7 +53,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
return
|
||||
}
|
||||
|
||||
const search = `?locale=${locale}&depth=0&fallback-locale=null&draft=true`
|
||||
const search = `?locale=${localeCode}&depth=0&fallback-locale=null&draft=true`
|
||||
let action
|
||||
let method = 'POST'
|
||||
|
||||
@@ -77,7 +76,7 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
},
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [submit, collectionSlug, globalSlug, serverURL, api, locale, id, forceDisable])
|
||||
}, [submit, collectionSlug, globalSlug, serverURL, api, localeCode, id, forceDisable])
|
||||
|
||||
useHotkey({ cmdCtrlKey: true, editDepth, keyCodes: ['s'] }, (e) => {
|
||||
e.preventDefault()
|
||||
@@ -140,7 +139,8 @@ export const PublishButton: React.FC<{ label?: string }> = ({ label: labelProp }
|
||||
? locale.label
|
||||
: locale.label && locale.label[i18n?.language]
|
||||
|
||||
const isActive = typeof locale === 'string' ? locale === code : locale.code === code
|
||||
const isActive =
|
||||
typeof locale === 'string' ? locale === localeCode : locale.code === localeCode
|
||||
|
||||
if (isActive) {
|
||||
return (
|
||||
|
||||
@@ -27,11 +27,11 @@ import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { mergeFieldStyles } from '../mergeFieldStyles.js'
|
||||
import { fieldBaseClass } from '../shared/index.js'
|
||||
import { createRelationMap } from './createRelationMap.js'
|
||||
import './index.scss'
|
||||
import { findOptionsByValue } from './findOptionsByValue.js'
|
||||
import { optionsReducer } from './optionsReducer.js'
|
||||
import { MultiValueLabel } from './select-components/MultiValueLabel/index.js'
|
||||
import { SingleValue } from './select-components/SingleValue/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const maxResultsPerRequest = 10
|
||||
|
||||
@@ -310,7 +310,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
// ///////////////////////////////////
|
||||
// Ensure we have an option for each value
|
||||
// ///////////////////////////////////
|
||||
|
||||
useIgnoredEffect(
|
||||
() => {
|
||||
const relationMap = createRelationMap({
|
||||
|
||||
@@ -485,6 +485,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
docPermissions,
|
||||
docPreferences,
|
||||
globalSlug,
|
||||
locale,
|
||||
operation,
|
||||
renderAllFields: true,
|
||||
schemaPath: collectionSlug ? collectionSlug : globalSlug,
|
||||
@@ -504,6 +505,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
getFormState,
|
||||
docPermissions,
|
||||
getDocPreferences,
|
||||
locale,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -21,73 +21,64 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
|
||||
const defaultLocale =
|
||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
const searchParams = useSearchParams()
|
||||
const localeFromParams = searchParams.get('locale')
|
||||
|
||||
const [localeCode, setLocaleCode] = useState<string>(localeFromParams || defaultLocale)
|
||||
const [localeCode, setLocaleCode] = useState<string>(defaultLocale)
|
||||
|
||||
const [locale, setLocale] = useState<Locale | null>(
|
||||
localization && findLocaleFromCode(localization, localeCode),
|
||||
)
|
||||
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
|
||||
const switchLocale = React.useCallback(
|
||||
async (newLocale: string) => {
|
||||
const locale: Locale = React.useMemo(() => {
|
||||
if (!localization) {
|
||||
return
|
||||
// TODO: return null V4
|
||||
return {} as Locale
|
||||
}
|
||||
|
||||
const localeToSet =
|
||||
localization.localeCodes.indexOf(newLocale) > -1 ? newLocale : defaultLocale
|
||||
|
||||
if (localeToSet !== localeCode) {
|
||||
setLocaleCode(localeToSet)
|
||||
setLocale(findLocaleFromCode(localization, localeToSet))
|
||||
try {
|
||||
if (user) {
|
||||
await setPreference('locale', localeToSet)
|
||||
}
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
}
|
||||
}
|
||||
},
|
||||
[localization, setPreference, user, defaultLocale, localeCode],
|
||||
return (
|
||||
findLocaleFromCode(localization, localeFromParams || localeCode) ||
|
||||
findLocaleFromCode(localization, defaultLocale)
|
||||
)
|
||||
}, [localeCode, localeFromParams, localization, defaultLocale])
|
||||
|
||||
useEffect(() => {
|
||||
async function setInitialLocale() {
|
||||
let localeToSet = defaultLocale
|
||||
|
||||
if (typeof localeFromParams === 'string') {
|
||||
localeToSet = localeFromParams
|
||||
} else if (user) {
|
||||
if (localization && user) {
|
||||
if (typeof localeFromParams !== 'string') {
|
||||
try {
|
||||
localeToSet = await getPreference<string>('locale')
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
const localeToSet = await getPreference<string>('locale')
|
||||
setLocaleCode(localeToSet)
|
||||
} catch (_) {
|
||||
setLocaleCode(defaultLocale)
|
||||
}
|
||||
} else {
|
||||
void setPreference(
|
||||
'locale',
|
||||
findLocaleFromCode(localization, localeFromParams)?.code || defaultLocale,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await switchLocale(localeToSet)
|
||||
}
|
||||
|
||||
void setInitialLocale()
|
||||
}, [
|
||||
defaultLocale,
|
||||
getPreference,
|
||||
localization,
|
||||
localeFromParams,
|
||||
setPreference,
|
||||
user,
|
||||
switchLocale,
|
||||
])
|
||||
}, [defaultLocale, getPreference, localization, localeFromParams, setPreference, user])
|
||||
|
||||
return <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the current locale object.
|
||||
* @deprecated A hook that returns the current locale object.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* #### 🚨 V4 Breaking Change
|
||||
* The `useLocale` return type now reflects `null | Locale` instead of `false | Locale`.
|
||||
*
|
||||
* **Old (V3):**
|
||||
* ```ts
|
||||
* const { code } = useLocale();
|
||||
* ```
|
||||
* **New (V4):**
|
||||
* ```ts
|
||||
* const locale = useLocale();
|
||||
* ```
|
||||
*/
|
||||
export const useLocale = (): Locale => useContext(LocaleContext)
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useSearchParams as useNextSearchParams } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
|
||||
export type SearchParamsContext = {
|
||||
searchParams: qs.ParsedQs
|
||||
stringifyParams: ({ params, replace }: { params: qs.ParsedQs; replace?: boolean }) => string
|
||||
@@ -28,8 +26,16 @@ const Context = createContext(initialContext)
|
||||
*/
|
||||
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const nextSearchParams = useNextSearchParams()
|
||||
const searchString = nextSearchParams.toString()
|
||||
|
||||
const [searchParams, setSearchParams] = React.useState(() => parseSearchParams(nextSearchParams))
|
||||
const searchParams = React.useMemo(
|
||||
() =>
|
||||
qs.parse(searchString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
}),
|
||||
[searchString],
|
||||
)
|
||||
|
||||
const stringifyParams = React.useCallback(
|
||||
({ params, replace = false }: { params: qs.ParsedQs; replace?: boolean }) => {
|
||||
@@ -44,10 +50,6 @@ export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({
|
||||
[searchParams],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
setSearchParams(parseSearchParams(nextSearchParams))
|
||||
}, [nextSearchParams])
|
||||
|
||||
return <Context.Provider value={{ searchParams, stringifyParams }}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ export const generateReleaseNotes = async (args: Args = {}): Promise<ChangelogRe
|
||||
|
||||
return sections
|
||||
},
|
||||
{} as Record<Sections | 'breaking', GitCommit[]>,
|
||||
{} as Record<'breaking' | Sections, GitCommit[]>,
|
||||
)
|
||||
|
||||
// Sort commits by scope, unscoped first
|
||||
|
||||
Reference in New Issue
Block a user