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