refactor: deprecates params and search params contexts (#9581)
As described in #9576, the `SearchParamsProvider` can become stale when navigating routes and relying on search params during initial render. This is because this context, along with the `ParamsProvider`, is duplicative to the internal lifecycle of `useSearchParams` and `useParams` from `next/navigation`– but always one render behind. Instead, we need to use the hooks directly from `next/navigation` as described in the jsdocs. This will also remove any abstraction over top the web standard for `URLSearchParams`. For this reason, these providers and their corresponding hooks have been marked with the deprecated flag and will continue to behave as they do now, but will be removed in the next major release. This PR replaces all internal reliance on these hooks with `next/navigation` as suggested, except for the `useParams` hook, which was never used in the first place. ```diff 'use client' - import { useSearchParams } from '@payloadcms/ui' + import { useSearchParams } from 'next/navigation' + import { parseSearchParams } from '@payloadcms/ui' export function MyClientComponent() { - const { searchParams } = useSearchParams() + const searchParams = useSearchParams() + const parsedParams = parseSearchParams(searchParams) // ... } ``` _MyClientComponent.tsx_
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { SanitizedConfig } from 'payload'
|
import type { SanitizedConfig } from 'payload'
|
||||||
|
|
||||||
import { useSearchParams } from '@payloadcms/ui'
|
|
||||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||||
import LinkImport from 'next/link.js'
|
import LinkImport from 'next/link.js'
|
||||||
import { useParams, usePathname } from 'next/navigation.js'
|
import { useParams, usePathname, useSearchParams } from 'next/navigation.js'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||||
@@ -30,12 +29,9 @@ export const DocumentTabLink: React.FC<{
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
|
||||||
const { searchParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
const locale =
|
const locale = searchParams.get('locale')
|
||||||
'locale' in searchParams && typeof searchParams.locale === 'string'
|
|
||||||
? searchParams.locale
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const [entityType, entitySlug, segmentThree, segmentFour, ...rest] = params.segments || []
|
const [entityType, entitySlug, segmentThree, segmentFour, ...rest] = params.segments || []
|
||||||
const isCollection = entityType === 'collections'
|
const isCollection = entityType === 'collections'
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import type { ClientCollectionConfig } from 'payload'
|
|||||||
|
|
||||||
import { Modal, useModal } from '@faceless-ui/modal'
|
import { Modal, useModal } from '@faceless-ui/modal'
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
import { useRouter } from 'next/navigation.js'
|
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||||
|
import * as qs from 'qs-esm'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import { useAuth } from '../../providers/Auth/index.js'
|
import { useAuth } from '../../providers/Auth/index.js'
|
||||||
import { useConfig } from '../../providers/Config/index.js'
|
import { useConfig } from '../../providers/Config/index.js'
|
||||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
|
||||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
import { requests } from '../../utilities/api.js'
|
import { requests } from '../../utilities/api.js'
|
||||||
@@ -41,7 +41,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
|||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
const [deleting, setDeleting] = useState(false)
|
const [deleting, setDeleting] = useState(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { searchParams, stringifyParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const { clearRouteCache } = useRouteCache()
|
const { clearRouteCache } = useRouteCache()
|
||||||
|
|
||||||
const collectionPermissions = permissions?.collections?.[slug]
|
const collectionPermissions = permissions?.collections?.[slug]
|
||||||
@@ -58,7 +58,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const queryWithSearch = mergeListSearchAndWhere({
|
const queryWithSearch = mergeListSearchAndWhere({
|
||||||
collectionConfig: collection,
|
collectionConfig: collection,
|
||||||
search: searchParams?.search as string,
|
search: searchParams.get('search'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const queryString = getQueryParams(queryWithSearch)
|
const queryString = getQueryParams(queryWithSearch)
|
||||||
@@ -85,21 +85,26 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
|||||||
label: getTranslation(successLabel, i18n),
|
label: getTranslation(successLabel, i18n),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (json?.errors.length > 0) {
|
if (json?.errors.length > 0) {
|
||||||
toast.error(json.message, {
|
toast.error(json.message, {
|
||||||
description: json.errors.map((error) => error.message).join('\n'),
|
description: json.errors.map((error) => error.message).join('\n'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAll()
|
toggleAll()
|
||||||
|
|
||||||
router.replace(
|
router.replace(
|
||||||
stringifyParams({
|
qs.stringify(
|
||||||
params: {
|
{
|
||||||
page: selectAll ? '1' : undefined,
|
page: selectAll ? '1' : undefined,
|
||||||
},
|
},
|
||||||
replace: true,
|
{ addQueryPrefix: true },
|
||||||
}),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
clearRouteCache()
|
clearRouteCache()
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +116,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
|||||||
addDefaultError()
|
addDefaultError()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
} catch (e) {
|
} catch (_err) {
|
||||||
return addDefaultError()
|
return addDefaultError()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -128,7 +133,6 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
|||||||
serverURL,
|
serverURL,
|
||||||
singular,
|
singular,
|
||||||
slug,
|
slug,
|
||||||
stringifyParams,
|
|
||||||
t,
|
t,
|
||||||
toggleAll,
|
toggleAll,
|
||||||
toggleModal,
|
toggleModal,
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import type { ClientCollectionConfig, FormState } from 'payload'
|
|||||||
|
|
||||||
import { useModal } from '@faceless-ui/modal'
|
import { useModal } from '@faceless-ui/modal'
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
import { useRouter } from 'next/navigation.js'
|
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||||
|
import * as qs from 'qs-esm'
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import type { FormProps } from '../../forms/Form/index.js'
|
import type { FormProps } from '../../forms/Form/index.js'
|
||||||
@@ -19,14 +20,14 @@ import { DocumentInfoProvider } from '../../providers/DocumentInfo/index.js'
|
|||||||
import { EditDepthProvider } from '../../providers/EditDepth/index.js'
|
import { EditDepthProvider } from '../../providers/EditDepth/index.js'
|
||||||
import { OperationContext } from '../../providers/Operation/index.js'
|
import { OperationContext } from '../../providers/Operation/index.js'
|
||||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
|
||||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||||
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
|
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
|
||||||
import { Drawer, DrawerToggler } from '../Drawer/index.js'
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import { Drawer, DrawerToggler } from '../Drawer/index.js'
|
||||||
import { FieldSelect } from '../FieldSelect/index.js'
|
import { FieldSelect } from '../FieldSelect/index.js'
|
||||||
|
|
||||||
const baseClass = 'edit-many'
|
const baseClass = 'edit-many'
|
||||||
@@ -125,7 +126,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
|||||||
const { count, getQueryParams, selectAll } = useSelection()
|
const { count, getQueryParams, selectAll } = useSelection()
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
const [selected, setSelected] = useState([])
|
const [selected, setSelected] = useState([])
|
||||||
const { searchParams, stringifyParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [initialState, setInitialState] = useState<FormState>()
|
const [initialState, setInitialState] = useState<FormState>()
|
||||||
const hasInitializedState = React.useRef(false)
|
const hasInitializedState = React.useRef(false)
|
||||||
@@ -195,7 +196,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
|||||||
const queryString = useMemo(() => {
|
const queryString = useMemo(() => {
|
||||||
const queryWithSearch = mergeListSearchAndWhere({
|
const queryWithSearch = mergeListSearchAndWhere({
|
||||||
collectionConfig: collection,
|
collectionConfig: collection,
|
||||||
search: searchParams?.search as string,
|
search: searchParams.get('search'),
|
||||||
})
|
})
|
||||||
|
|
||||||
return getQueryParams(queryWithSearch)
|
return getQueryParams(queryWithSearch)
|
||||||
@@ -203,9 +204,13 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
|||||||
|
|
||||||
const onSuccess = () => {
|
const onSuccess = () => {
|
||||||
router.replace(
|
router.replace(
|
||||||
stringifyParams({
|
qs.stringify(
|
||||||
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
|
{
|
||||||
}),
|
...parseSearchParams(searchParams),
|
||||||
|
page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined,
|
||||||
|
},
|
||||||
|
{ addQueryPrefix: true },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||||
closeModal(drawerSlug)
|
closeModal(drawerSlug)
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
|
import { useSearchParams } from 'next/navigation.js'
|
||||||
|
import * as qs from 'qs-esm'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { useConfig } from '../../providers/Config/index.js'
|
import { useConfig } from '../../providers/Config/index.js'
|
||||||
import { useLocale } from '../../providers/Locale/index.js'
|
import { useLocale } from '../../providers/Locale/index.js'
|
||||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
import { Popup, PopupList } from '../Popup/index.js'
|
import { Popup, PopupList } from '../Popup/index.js'
|
||||||
import './index.scss'
|
|
||||||
import { LocalizerLabel } from './LocalizerLabel/index.js'
|
import { LocalizerLabel } from './LocalizerLabel/index.js'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
const baseClass = 'localizer'
|
const baseClass = 'localizer'
|
||||||
|
|
||||||
@@ -18,10 +20,10 @@ export const Localizer: React.FC<{
|
|||||||
const { className } = props
|
const { className } = props
|
||||||
const { config } = useConfig()
|
const { config } = useConfig()
|
||||||
const { localization } = config
|
const { localization } = config
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
const { i18n } = useTranslation()
|
const { i18n } = useTranslation()
|
||||||
const locale = useLocale()
|
const locale = useLocale()
|
||||||
const { stringifyParams } = useSearchParams()
|
|
||||||
|
|
||||||
if (localization) {
|
if (localization) {
|
||||||
const { locales } = localization
|
const { locales } = localization
|
||||||
@@ -39,11 +41,13 @@ export const Localizer: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<PopupList.Button
|
<PopupList.Button
|
||||||
active={locale.code === localeOption.code}
|
active={locale.code === localeOption.code}
|
||||||
href={stringifyParams({
|
href={qs.stringify(
|
||||||
params: {
|
{
|
||||||
|
...parseSearchParams(searchParams),
|
||||||
locale: localeOption.code,
|
locale: localeOption.code,
|
||||||
},
|
},
|
||||||
})}
|
{ addQueryPrefix: true },
|
||||||
|
)}
|
||||||
key={localeOption.code}
|
key={localeOption.code}
|
||||||
onClick={close}
|
onClick={close}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ import type { ClientCollectionConfig } from 'payload'
|
|||||||
|
|
||||||
import { Modal, useModal } from '@faceless-ui/modal'
|
import { Modal, useModal } from '@faceless-ui/modal'
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
import { useRouter } from 'next/navigation.js'
|
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||||
|
import * as qs from 'qs-esm'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import { useAuth } from '../../providers/Auth/index.js'
|
import { useAuth } from '../../providers/Auth/index.js'
|
||||||
import { useConfig } from '../../providers/Config/index.js'
|
import { useConfig } from '../../providers/Config/index.js'
|
||||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
|
||||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
import { requests } from '../../utilities/api.js'
|
import { requests } from '../../utilities/api.js'
|
||||||
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
import { Button } from '../Button/index.js'
|
import { Button } from '../Button/index.js'
|
||||||
import { Pill } from '../Pill/index.js'
|
import { Pill } from '../Pill/index.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
@@ -41,7 +42,7 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
|||||||
const { getQueryParams, selectAll } = useSelection()
|
const { getQueryParams, selectAll } = useSelection()
|
||||||
const [submitted, setSubmitted] = useState(false)
|
const [submitted, setSubmitted] = useState(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { stringifyParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
const collectionPermissions = permissions?.collections?.[slug]
|
const collectionPermissions = permissions?.collections?.[slug]
|
||||||
const hasPermission = collectionPermissions?.update
|
const hasPermission = collectionPermissions?.update
|
||||||
@@ -82,17 +83,21 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
|||||||
label: getTranslation(successLabel, i18n),
|
label: getTranslation(successLabel, i18n),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (json?.errors.length > 0) {
|
if (json?.errors.length > 0) {
|
||||||
toast.error(json.message, {
|
toast.error(json.message, {
|
||||||
description: json.errors.map((error) => error.message).join('\n'),
|
description: json.errors.map((error) => error.message).join('\n'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
router.replace(
|
router.replace(
|
||||||
stringifyParams({
|
qs.stringify(
|
||||||
params: {
|
{
|
||||||
|
...parseSearchParams(searchParams),
|
||||||
page: selectAll ? '1' : undefined,
|
page: selectAll ? '1' : undefined,
|
||||||
},
|
},
|
||||||
}),
|
{ addQueryPrefix: true },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||||
@@ -110,21 +115,21 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
addDefaultError,
|
serverURL,
|
||||||
api,
|
api,
|
||||||
|
slug,
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
i18n,
|
i18n,
|
||||||
|
toggleModal,
|
||||||
modalSlug,
|
modalSlug,
|
||||||
plural,
|
plural,
|
||||||
selectAll,
|
|
||||||
serverURL,
|
|
||||||
singular,
|
singular,
|
||||||
slug,
|
|
||||||
t,
|
t,
|
||||||
toggleModal,
|
|
||||||
router,
|
router,
|
||||||
stringifyParams,
|
searchParams,
|
||||||
|
selectAll,
|
||||||
clearRouteCache,
|
clearRouteCache,
|
||||||
|
addDefaultError,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { OptionObject, SanitizedCollectionConfig } from 'payload'
|
|||||||
|
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
// TODO: abstract the `next/navigation` dependency out from this component
|
// TODO: abstract the `next/navigation` dependency out from this component
|
||||||
import { usePathname, useRouter } from 'next/navigation.js'
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation.js'
|
||||||
import { sortableFieldTypes } from 'payload'
|
import { sortableFieldTypes } from 'payload'
|
||||||
import { fieldAffectsData } from 'payload/shared'
|
import { fieldAffectsData } from 'payload/shared'
|
||||||
import * as qs from 'qs-esm'
|
import * as qs from 'qs-esm'
|
||||||
@@ -18,7 +18,6 @@ export type SortComplexProps = {
|
|||||||
|
|
||||||
import type { Option } from '../ReactSelect/index.js'
|
import type { Option } from '../ReactSelect/index.js'
|
||||||
|
|
||||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
import { ReactSelect } from '../ReactSelect/index.js'
|
import { ReactSelect } from '../ReactSelect/index.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
@@ -30,7 +29,7 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { searchParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
const [sortOptions, setSortOptions] = useState<OptionObject[]>()
|
const [sortOptions, setSortOptions] = useState<OptionObject[]>()
|
||||||
|
|
||||||
@@ -58,7 +57,7 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
|
|||||||
handleChange(newSortValue)
|
handleChange(newSortValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams.sort !== newSortValue && modifySearchQuery) {
|
if (searchParams.get('sort') !== newSortValue && modifySearchQuery) {
|
||||||
const search = qs.stringify(
|
const search = qs.stringify(
|
||||||
{
|
{
|
||||||
...searchParams,
|
...searchParams,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Modal, useModal } from '@faceless-ui/modal'
|
import { Modal, useModal } from '@faceless-ui/modal'
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
import { useRouter } from 'next/navigation.js'
|
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||||
|
import * as qs from 'qs-esm'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { useAuth } from '../../providers/Auth/index.js'
|
import { useAuth } from '../../providers/Auth/index.js'
|
||||||
import { useConfig } from '../../providers/Config/index.js'
|
import { useConfig } from '../../providers/Config/index.js'
|
||||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
|
||||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
import { requests } from '../../utilities/api.js'
|
import { requests } from '../../utilities/api.js'
|
||||||
@@ -21,6 +21,8 @@ import type { ClientCollectionConfig } from 'payload'
|
|||||||
|
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
|
|
||||||
export type UnpublishManyProps = {
|
export type UnpublishManyProps = {
|
||||||
collection: ClientCollectionConfig
|
collection: ClientCollectionConfig
|
||||||
}
|
}
|
||||||
@@ -38,9 +40,9 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
|||||||
const { permissions } = useAuth()
|
const { permissions } = useAuth()
|
||||||
const { toggleModal } = useModal()
|
const { toggleModal } = useModal()
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
const { getQueryParams, selectAll } = useSelection()
|
const { getQueryParams, selectAll } = useSelection()
|
||||||
const [submitted, setSubmitted] = useState(false)
|
const [submitted, setSubmitted] = useState(false)
|
||||||
const { stringifyParams } = useSearchParams()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { clearRouteCache } = useRouteCache()
|
const { clearRouteCache } = useRouteCache()
|
||||||
|
|
||||||
@@ -86,11 +88,13 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
router.replace(
|
router.replace(
|
||||||
stringifyParams({
|
qs.stringify(
|
||||||
params: {
|
{
|
||||||
|
...parseSearchParams(searchParams),
|
||||||
page: selectAll ? '1' : undefined,
|
page: selectAll ? '1' : undefined,
|
||||||
},
|
},
|
||||||
}),
|
{ addQueryPrefix: true },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||||
return null
|
return null
|
||||||
@@ -102,26 +106,26 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
|||||||
addDefaultError()
|
addDefaultError()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
} catch (e) {
|
} catch (_err) {
|
||||||
return addDefaultError()
|
return addDefaultError()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
addDefaultError,
|
serverURL,
|
||||||
api,
|
api,
|
||||||
|
slug,
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
i18n,
|
i18n,
|
||||||
|
toggleModal,
|
||||||
modalSlug,
|
modalSlug,
|
||||||
plural,
|
plural,
|
||||||
selectAll,
|
|
||||||
serverURL,
|
|
||||||
singular,
|
singular,
|
||||||
slug,
|
|
||||||
t,
|
t,
|
||||||
toggleModal,
|
|
||||||
router,
|
router,
|
||||||
|
searchParams,
|
||||||
|
selectAll,
|
||||||
clearRouteCache,
|
clearRouteCache,
|
||||||
stringifyParams,
|
addDefaultError,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useS
|
|||||||
import type { Column } from '../../elements/Table/index.js'
|
import type { Column } from '../../elements/Table/index.js'
|
||||||
|
|
||||||
import { useListDrawerContext } from '../../elements/ListDrawer/Provider.js'
|
import { useListDrawerContext } from '../../elements/ListDrawer/Provider.js'
|
||||||
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
import { usePreferences } from '../Preferences/index.js'
|
import { usePreferences } from '../Preferences/index.js'
|
||||||
import { createParams } from '../SearchParams/index.js'
|
|
||||||
|
|
||||||
export type ColumnPreferences = Pick<Column, 'accessor' | 'active'>[]
|
export type ColumnPreferences = Pick<Column, 'accessor' | 'active'>[]
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { setPreference } = usePreferences()
|
const { setPreference } = usePreferences()
|
||||||
const rawSearchParams = useSearchParams()
|
const rawSearchParams = useSearchParams()
|
||||||
const searchParams = useMemo(() => createParams(rawSearchParams), [rawSearchParams])
|
const searchParams = useMemo(() => parseSearchParams(rawSearchParams), [rawSearchParams])
|
||||||
|
|
||||||
const { onQueryChange } = useListDrawerContext()
|
const { onQueryChange } = useListDrawerContext()
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import type { Locale } from 'payload'
|
import type { Locale } from 'payload'
|
||||||
|
|
||||||
|
import { useSearchParams } from 'next/navigation.js'
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { findLocaleFromCode } from '../../utilities/findLocaleFromCode.js'
|
import { findLocaleFromCode } from '../../utilities/findLocaleFromCode.js'
|
||||||
import { useAuth } from '../Auth/index.js'
|
import { useAuth } from '../Auth/index.js'
|
||||||
import { useConfig } from '../Config/index.js'
|
import { useConfig } from '../Config/index.js'
|
||||||
import { usePreferences } from '../Preferences/index.js'
|
import { usePreferences } from '../Preferences/index.js'
|
||||||
import { useSearchParams } from '../SearchParams/index.js'
|
|
||||||
|
|
||||||
const LocaleContext = createContext({} as Locale)
|
const LocaleContext = createContext({} as Locale)
|
||||||
|
|
||||||
@@ -21,12 +21,10 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
|
|||||||
const defaultLocale =
|
const defaultLocale =
|
||||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||||
|
|
||||||
const { searchParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const localeFromParams = searchParams?.locale
|
const localeFromParams = searchParams.get('locale')
|
||||||
|
|
||||||
const [localeCode, setLocaleCode] = useState<string>(
|
const [localeCode, setLocaleCode] = useState<string>(localeFromParams || defaultLocale)
|
||||||
(localeFromParams as string) || defaultLocale,
|
|
||||||
)
|
|
||||||
|
|
||||||
const [locale, setLocale] = useState<Locale | null>(
|
const [locale, setLocale] = useState<Locale | null>(
|
||||||
localization && findLocaleFromCode(localization, localeCode),
|
localization && findLocaleFromCode(localization, localeCode),
|
||||||
|
|||||||
@@ -8,10 +8,25 @@ interface IParamsContext extends Params {}
|
|||||||
|
|
||||||
const Context = createContext<IParamsContext>({} as IParamsContext)
|
const Context = createContext<IParamsContext>({} as IParamsContext)
|
||||||
|
|
||||||
// TODO: abstract the `next/navigation` dependency out from this provider so that it can be used in other contexts
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* The ParamsProvider is deprecated and will be removed in the next major release. Instead, use the `useParams` hook from `next/navigation` directly. See https://github.com/payloadcms/payload/pull/9581.
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import { useParams } from 'next/navigation'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const ParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
export const ParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||||
const params = useNextParams()
|
const params = useNextParams()
|
||||||
return <Context.Provider value={params}>{children}</Context.Provider>
|
return <Context.Provider value={params}>{children}</Context.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* The `useParams` hook is deprecated and will be removed in the next major release. Instead, use the `useParams` hook from `next/navigation` directly. See https://github.com/payloadcms/payload/pull/9581.
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import { useParams } from 'next/navigation'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const useParams = (): IParamsContext => useContext(Context)
|
export const useParams = (): IParamsContext => useContext(Context)
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { ReadonlyURLSearchParams } from 'next/navigation.js'
|
|
||||||
|
|
||||||
import { useSearchParams as useNextSearchParams } from 'next/navigation.js'
|
import { useSearchParams as useNextSearchParams } from 'next/navigation.js'
|
||||||
import * as qs from 'qs-esm'
|
import * as qs from 'qs-esm'
|
||||||
import React, { createContext, useContext } from 'react'
|
import React, { createContext, useContext } from 'react'
|
||||||
|
|
||||||
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
|
|
||||||
export type SearchParamsContext = {
|
export type SearchParamsContext = {
|
||||||
searchParams: qs.ParsedQs
|
searchParams: qs.ParsedQs
|
||||||
stringifyParams: ({ params, replace }: { params: State; replace?: boolean }) => string
|
stringifyParams: ({ params, replace }: { params: qs.ParsedQs; replace?: boolean }) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = qs.ParsedQs
|
|
||||||
|
|
||||||
const initialContext: SearchParamsContext = {
|
const initialContext: SearchParamsContext = {
|
||||||
searchParams: {},
|
searchParams: {},
|
||||||
stringifyParams: () => '',
|
stringifyParams: () => '',
|
||||||
@@ -19,23 +18,21 @@ const initialContext: SearchParamsContext = {
|
|||||||
|
|
||||||
const Context = createContext(initialContext)
|
const Context = createContext(initialContext)
|
||||||
|
|
||||||
export function createParams(params: ReadonlyURLSearchParams): State {
|
/**
|
||||||
const search = params.toString()
|
* @deprecated
|
||||||
|
* The SearchParamsProvider is deprecated and will be removed in the next major release. Instead, use the `useSearchParams` hook from `next/navigation` directly. See https://github.com/payloadcms/payload/pull/9581.
|
||||||
return qs.parse(search, {
|
* @example
|
||||||
depth: 10,
|
* ```tsx
|
||||||
ignoreQueryPrefix: true,
|
* import { useSearchParams } from 'next/navigation'
|
||||||
})
|
* ```
|
||||||
}
|
*/
|
||||||
|
|
||||||
// TODO: this provider should likely be marked as deprecated and then removed in the next major release
|
|
||||||
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||||
const nextSearchParams = useNextSearchParams()
|
const nextSearchParams = useNextSearchParams()
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = React.useState(() => createParams(nextSearchParams))
|
const [searchParams, setSearchParams] = React.useState(() => parseSearchParams(nextSearchParams))
|
||||||
|
|
||||||
const stringifyParams = React.useCallback(
|
const stringifyParams = React.useCallback(
|
||||||
({ params, replace = false }: { params: State; replace?: boolean }) => {
|
({ params, replace = false }: { params: qs.ParsedQs; replace?: boolean }) => {
|
||||||
return qs.stringify(
|
return qs.stringify(
|
||||||
{
|
{
|
||||||
...(replace ? {} : searchParams),
|
...(replace ? {} : searchParams),
|
||||||
@@ -48,10 +45,23 @@ export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setSearchParams(createParams(nextSearchParams))
|
setSearchParams(parseSearchParams(nextSearchParams))
|
||||||
}, [nextSearchParams])
|
}, [nextSearchParams])
|
||||||
|
|
||||||
return <Context.Provider value={{ searchParams, stringifyParams }}>{children}</Context.Provider>
|
return <Context.Provider value={{ searchParams, stringifyParams }}>{children}</Context.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* The `useSearchParams` hook is deprecated and will be removed in the next major release. Instead, use the `useSearchParams` hook from `next/navigation` directly. See https://github.com/payloadcms/payload/pull/9581.
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import { useSearchParams } from 'next/navigation'
|
||||||
|
* ```
|
||||||
|
* If you need to parse the `where` query, you can do so with the `parseSearchParams` utility.
|
||||||
|
* ```tsx
|
||||||
|
* import { parseSearchParams } from '@payloadcms/ui'
|
||||||
|
* const parsedSearchParams = parseSearchParams(searchParams)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const useSearchParams = (): SearchParamsContext => useContext(Context)
|
export const useSearchParams = (): SearchParamsContext => useContext(Context)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { ClientUser, Where } from 'payload'
|
import type { ClientUser, Where } from 'payload'
|
||||||
|
|
||||||
|
import { useSearchParams } from 'next/navigation.js'
|
||||||
import * as qs from 'qs-esm'
|
import * as qs from 'qs-esm'
|
||||||
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||||
import { useLocale } from '../Locale/index.js'
|
import { useLocale } from '../Locale/index.js'
|
||||||
import { useSearchParams } from '../SearchParams/index.js'
|
|
||||||
|
|
||||||
export enum SelectAllStatus {
|
export enum SelectAllStatus {
|
||||||
AllAvailable = 'allAvailable',
|
AllAvailable = 'allAvailable',
|
||||||
@@ -51,7 +52,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
|||||||
|
|
||||||
const [selectAll, setSelectAll] = useState<SelectAllStatus>(SelectAllStatus.None)
|
const [selectAll, setSelectAll] = useState<SelectAllStatus>(SelectAllStatus.None)
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
const { searchParams } = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
const toggleAll = useCallback(
|
const toggleAll = useCallback(
|
||||||
(allAvailable = false) => {
|
(allAvailable = false) => {
|
||||||
@@ -110,7 +111,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
|||||||
let where: Where
|
let where: Where
|
||||||
|
|
||||||
if (selectAll === SelectAllStatus.AllAvailable) {
|
if (selectAll === SelectAllStatus.AllAvailable) {
|
||||||
const params = searchParams?.where as Where
|
const params = parseSearchParams(searchParams)?.where as Where
|
||||||
|
|
||||||
where = params || {
|
where = params || {
|
||||||
id: { not_equals: '' },
|
id: { not_equals: '' },
|
||||||
|
|||||||
12
packages/ui/src/utilities/parseSearchParams.ts
Normal file
12
packages/ui/src/utilities/parseSearchParams.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { ReadonlyURLSearchParams } from 'next/navigation.js'
|
||||||
|
|
||||||
|
import * as qs from 'qs-esm'
|
||||||
|
|
||||||
|
export function parseSearchParams(params: ReadonlyURLSearchParams): qs.ParsedQs {
|
||||||
|
const search = params.toString()
|
||||||
|
|
||||||
|
return qs.parse(search, {
|
||||||
|
depth: 10,
|
||||||
|
ignoreQueryPrefix: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user