diff --git a/examples/custom-server/next.config.ts b/examples/custom-server/next.config.ts index 9728c00cd66..7dcc112bd0a 100644 --- a/examples/custom-server/next.config.ts +++ b/examples/custom-server/next.config.ts @@ -1,4 +1,4 @@ -import { withPayload } from "@payloadcms/next/withPayload"; +import { withPayload } from '@payloadcms/next/withPayload' import type { NextConfig } from 'next' const nextConfig: NextConfig = {} diff --git a/examples/custom-server/package.json b/examples/custom-server/package.json index 5e635fe1529..ed0f444ecbf 100644 --- a/examples/custom-server/package.json +++ b/examples/custom-server/package.json @@ -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", diff --git a/examples/custom-server/src/app/(app)/b/page.tsx b/examples/custom-server/src/app/(app)/b/page.tsx index bd162b849ef..ca1631c34e0 100644 --- a/examples/custom-server/src/app/(app)/b/page.tsx +++ b/examples/custom-server/src/app/(app)/b/page.tsx @@ -1,3 +1,3 @@ export default function B() { - return
b
; + return
b
} diff --git a/examples/custom-server/src/app/(app)/layout.tsx b/examples/custom-server/src/app/(app)/layout.tsx index 225b6038d7f..0b1163df895 100644 --- a/examples/custom-server/src/app/(app)/layout.tsx +++ b/examples/custom-server/src/app/(app)/layout.tsx @@ -1,11 +1,7 @@ -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} - ); + ) } diff --git a/examples/custom-server/src/app/(payload)/admin/importMap.js b/examples/custom-server/src/app/(payload)/admin/importMap.js index a3bc0e84f8d..8ef7021383f 100644 --- a/examples/custom-server/src/app/(payload)/admin/importMap.js +++ b/examples/custom-server/src/app/(payload)/admin/importMap.js @@ -1,5 +1 @@ - - -export const importMap = { - -} +export const importMap = {} diff --git a/packages/ui/src/elements/PublishButton/index.tsx b/packages/ui/src/elements/PublishButton/index.tsx index f046817fe5f..b5035f14b4e 100644 --- a/packages/ui/src/elements/PublishButton/index.tsx +++ b/packages/ui/src/elements/PublishButton/index.tsx @@ -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 ( diff --git a/packages/ui/src/fields/Relationship/index.tsx b/packages/ui/src/fields/Relationship/index.tsx index bbfa1dece3c..b727809370b 100644 --- a/packages/ui/src/fields/Relationship/index.tsx +++ b/packages/ui/src/fields/Relationship/index.tsx @@ -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({ diff --git a/packages/ui/src/forms/Form/index.tsx b/packages/ui/src/forms/Form/index.tsx index 7ab648cd75d..237cec9dfa7 100644 --- a/packages/ui/src/forms/Form/index.tsx +++ b/packages/ui/src/forms/Form/index.tsx @@ -485,6 +485,7 @@ export const Form: React.FC = (props) => { docPermissions, docPreferences, globalSlug, + locale, operation, renderAllFields: true, schemaPath: collectionSlug ? collectionSlug : globalSlug, @@ -504,6 +505,7 @@ export const Form: React.FC = (props) => { getFormState, docPermissions, getDocPreferences, + locale, ], ) diff --git a/packages/ui/src/providers/Locale/index.tsx b/packages/ui/src/providers/Locale/index.tsx index 1dccc02d45e..3b0cfcb6f98 100644 --- a/packages/ui/src/providers/Locale/index.tsx +++ b/packages/ui/src/providers/Locale/index.tsx @@ -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(localeFromParams || defaultLocale) - - const [locale, setLocale] = useState( - localization && findLocaleFromCode(localization, localeCode), - ) - - const { getPreference, setPreference } = usePreferences() + const [localeCode, setLocaleCode] = useState(defaultLocale) - const switchLocale = React.useCallback( - async (newLocale: string) => { - if (!localization) { - return - } + const locale: Locale = React.useMemo(() => { + if (!localization) { + // 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) { - try { - localeToSet = await getPreference('locale') - } catch (error) { - // swallow error + if (localization && user) { + if (typeof localeFromParams !== 'string') { + try { + const localeToSet = await getPreference('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 {children} } /** - * 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) diff --git a/packages/ui/src/providers/SearchParams/index.tsx b/packages/ui/src/providers/SearchParams/index.tsx index 5a14b1d3f3d..ed51f771283 100644 --- a/packages/ui/src/providers/SearchParams/index.tsx +++ b/packages/ui/src/providers/SearchParams/index.tsx @@ -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 {children} } diff --git a/scripts/utils/generateReleaseNotes.ts b/scripts/utils/generateReleaseNotes.ts index 4d5c1eb4dfb..e1ca11caedd 100755 --- a/scripts/utils/generateReleaseNotes.ts +++ b/scripts/utils/generateReleaseNotes.ts @@ -112,7 +112,7 @@ export const generateReleaseNotes = async (args: Args = {}): Promise, + {} as Record<'breaking' | Sections, GitCommit[]>, ) // Sort commits by scope, unscoped first