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'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import { useSearchParams } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
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'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
@@ -30,12 +29,9 @@ export const DocumentTabLink: React.FC<{
|
||||
const pathname = usePathname()
|
||||
const params = useParams()
|
||||
|
||||
const { searchParams } = useSearchParams()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const locale =
|
||||
'locale' in searchParams && typeof searchParams.locale === 'string'
|
||||
? searchParams.locale
|
||||
: undefined
|
||||
const locale = searchParams.get('locale')
|
||||
|
||||
const [entityType, entitySlug, segmentThree, segmentFour, ...rest] = params.segments || []
|
||||
const isCollection = entityType === 'collections'
|
||||
|
||||
@@ -3,14 +3,14 @@ import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
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 { toast } from 'sonner'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/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 { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { requests } from '../../utilities/api.js'
|
||||
@@ -41,7 +41,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
const { i18n, t } = useTranslation()
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const router = useRouter()
|
||||
const { searchParams, stringifyParams } = useSearchParams()
|
||||
const searchParams = useSearchParams()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
@@ -58,7 +58,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
|
||||
const queryWithSearch = mergeListSearchAndWhere({
|
||||
collectionConfig: collection,
|
||||
search: searchParams?.search as string,
|
||||
search: searchParams.get('search'),
|
||||
})
|
||||
|
||||
const queryString = getQueryParams(queryWithSearch)
|
||||
@@ -85,21 +85,26 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
toggleAll()
|
||||
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
qs.stringify(
|
||||
{
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
replace: true,
|
||||
}),
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -111,7 +116,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (e) {
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
@@ -128,7 +133,6 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
serverURL,
|
||||
singular,
|
||||
slug,
|
||||
stringifyParams,
|
||||
t,
|
||||
toggleAll,
|
||||
toggleModal,
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { ClientCollectionConfig, FormState } from 'payload'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
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 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 { OperationContext } from '../../providers/Operation/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 { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
|
||||
import { Drawer, DrawerToggler } from '../Drawer/index.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import './index.scss'
|
||||
import { Drawer, DrawerToggler } from '../Drawer/index.js'
|
||||
import { FieldSelect } from '../FieldSelect/index.js'
|
||||
|
||||
const baseClass = 'edit-many'
|
||||
@@ -125,7 +126,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
const { count, getQueryParams, selectAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [selected, setSelected] = useState([])
|
||||
const { searchParams, stringifyParams } = useSearchParams()
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
const hasInitializedState = React.useRef(false)
|
||||
@@ -195,7 +196,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
const queryString = useMemo(() => {
|
||||
const queryWithSearch = mergeListSearchAndWhere({
|
||||
collectionConfig: collection,
|
||||
search: searchParams?.search as string,
|
||||
search: searchParams.get('search'),
|
||||
})
|
||||
|
||||
return getQueryParams(queryWithSearch)
|
||||
@@ -203,9 +204,13 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
|
||||
const onSuccess = () => {
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
|
||||
}),
|
||||
qs.stringify(
|
||||
{
|
||||
...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
|
||||
closeModal(drawerSlug)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
'use client'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useSearchParams } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
import React from 'react'
|
||||
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useLocale } from '../../providers/Locale/index.js'
|
||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import { Popup, PopupList } from '../Popup/index.js'
|
||||
import './index.scss'
|
||||
import { LocalizerLabel } from './LocalizerLabel/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'localizer'
|
||||
|
||||
@@ -18,10 +20,10 @@ export const Localizer: React.FC<{
|
||||
const { className } = props
|
||||
const { config } = useConfig()
|
||||
const { localization } = config
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
|
||||
if (localization) {
|
||||
const { locales } = localization
|
||||
@@ -39,11 +41,13 @@ export const Localizer: React.FC<{
|
||||
return (
|
||||
<PopupList.Button
|
||||
active={locale.code === localeOption.code}
|
||||
href={stringifyParams({
|
||||
params: {
|
||||
href={qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
locale: localeOption.code,
|
||||
},
|
||||
})}
|
||||
{ addQueryPrefix: true },
|
||||
)}
|
||||
key={localeOption.code}
|
||||
onClick={close}
|
||||
>
|
||||
|
||||
@@ -3,17 +3,18 @@ import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
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 { toast } from 'sonner'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/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 { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { requests } from '../../utilities/api.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import { Button } from '../Button/index.js'
|
||||
import { Pill } from '../Pill/index.js'
|
||||
import './index.scss'
|
||||
@@ -41,7 +42,7 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
||||
const { getQueryParams, selectAll } = useSelection()
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const router = useRouter()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasPermission = collectionPermissions?.update
|
||||
@@ -82,17 +83,21 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
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
|
||||
@@ -110,21 +115,21 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
||||
}
|
||||
})
|
||||
}, [
|
||||
addDefaultError,
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
toggleModal,
|
||||
modalSlug,
|
||||
plural,
|
||||
selectAll,
|
||||
serverURL,
|
||||
singular,
|
||||
slug,
|
||||
t,
|
||||
toggleModal,
|
||||
router,
|
||||
stringifyParams,
|
||||
searchParams,
|
||||
selectAll,
|
||||
clearRouteCache,
|
||||
addDefaultError,
|
||||
])
|
||||
|
||||
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { OptionObject, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
// 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 { fieldAffectsData } from 'payload/shared'
|
||||
import * as qs from 'qs-esm'
|
||||
@@ -18,7 +18,6 @@ export type SortComplexProps = {
|
||||
|
||||
import type { Option } from '../ReactSelect/index.js'
|
||||
|
||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { ReactSelect } from '../ReactSelect/index.js'
|
||||
import './index.scss'
|
||||
@@ -30,7 +29,7 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
|
||||
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { searchParams } = useSearchParams()
|
||||
const searchParams = useSearchParams()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [sortOptions, setSortOptions] = useState<OptionObject[]>()
|
||||
|
||||
@@ -58,7 +57,7 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
|
||||
handleChange(newSortValue)
|
||||
}
|
||||
|
||||
if (searchParams.sort !== newSortValue && modifySearchQuery) {
|
||||
if (searchParams.get('sort') !== newSortValue && modifySearchQuery) {
|
||||
const search = qs.stringify(
|
||||
{
|
||||
...searchParams,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
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 { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/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 { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { requests } from '../../utilities/api.js'
|
||||
@@ -21,6 +21,8 @@ import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
|
||||
export type UnpublishManyProps = {
|
||||
collection: ClientCollectionConfig
|
||||
}
|
||||
@@ -38,9 +40,9 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
||||
const { permissions } = useAuth()
|
||||
const { toggleModal } = useModal()
|
||||
const { i18n, t } = useTranslation()
|
||||
const searchParams = useSearchParams()
|
||||
const { getQueryParams, selectAll } = useSelection()
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const { stringifyParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
|
||||
@@ -86,11 +88,13 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
||||
})
|
||||
}
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
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
|
||||
return null
|
||||
@@ -102,26 +106,26 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (e) {
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
}, [
|
||||
addDefaultError,
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
toggleModal,
|
||||
modalSlug,
|
||||
plural,
|
||||
selectAll,
|
||||
serverURL,
|
||||
singular,
|
||||
slug,
|
||||
t,
|
||||
toggleModal,
|
||||
router,
|
||||
searchParams,
|
||||
selectAll,
|
||||
clearRouteCache,
|
||||
stringifyParams,
|
||||
addDefaultError,
|
||||
])
|
||||
|
||||
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 { useListDrawerContext } from '../../elements/ListDrawer/Provider.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import { usePreferences } from '../Preferences/index.js'
|
||||
import { createParams } from '../SearchParams/index.js'
|
||||
|
||||
export type ColumnPreferences = Pick<Column, 'accessor' | 'active'>[]
|
||||
|
||||
@@ -59,7 +59,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
const router = useRouter()
|
||||
const { setPreference } = usePreferences()
|
||||
const rawSearchParams = useSearchParams()
|
||||
const searchParams = useMemo(() => createParams(rawSearchParams), [rawSearchParams])
|
||||
const searchParams = useMemo(() => parseSearchParams(rawSearchParams), [rawSearchParams])
|
||||
|
||||
const { onQueryChange } = useListDrawerContext()
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import type { Locale } from 'payload'
|
||||
|
||||
import { useSearchParams } from 'next/navigation.js'
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { findLocaleFromCode } from '../../utilities/findLocaleFromCode.js'
|
||||
import { useAuth } from '../Auth/index.js'
|
||||
import { useConfig } from '../Config/index.js'
|
||||
import { usePreferences } from '../Preferences/index.js'
|
||||
import { useSearchParams } from '../SearchParams/index.js'
|
||||
|
||||
const LocaleContext = createContext({} as Locale)
|
||||
|
||||
@@ -21,12 +21,10 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
|
||||
const defaultLocale =
|
||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||
|
||||
const { searchParams } = useSearchParams()
|
||||
const localeFromParams = searchParams?.locale
|
||||
const searchParams = useSearchParams()
|
||||
const localeFromParams = searchParams.get('locale')
|
||||
|
||||
const [localeCode, setLocaleCode] = useState<string>(
|
||||
(localeFromParams as string) || defaultLocale,
|
||||
)
|
||||
const [localeCode, setLocaleCode] = useState<string>(localeFromParams || defaultLocale)
|
||||
|
||||
const [locale, setLocale] = useState<Locale | null>(
|
||||
localization && findLocaleFromCode(localization, localeCode),
|
||||
|
||||
@@ -8,10 +8,25 @@ interface IParamsContext extends Params {}
|
||||
|
||||
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 }) => {
|
||||
const params = useNextParams()
|
||||
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)
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
'use client'
|
||||
import type { ReadonlyURLSearchParams } from 'next/navigation.js'
|
||||
|
||||
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: State; replace?: boolean }) => string
|
||||
stringifyParams: ({ params, replace }: { params: qs.ParsedQs; replace?: boolean }) => string
|
||||
}
|
||||
|
||||
export type State = qs.ParsedQs
|
||||
|
||||
const initialContext: SearchParamsContext = {
|
||||
searchParams: {},
|
||||
stringifyParams: () => '',
|
||||
@@ -19,23 +18,21 @@ const initialContext: SearchParamsContext = {
|
||||
|
||||
const Context = createContext(initialContext)
|
||||
|
||||
export function createParams(params: ReadonlyURLSearchParams): State {
|
||||
const search = params.toString()
|
||||
|
||||
return qs.parse(search, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: this provider should likely be marked as deprecated and then removed in the next major release
|
||||
/**
|
||||
* @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.
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { useSearchParams } from 'next/navigation'
|
||||
* ```
|
||||
*/
|
||||
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const nextSearchParams = useNextSearchParams()
|
||||
|
||||
const [searchParams, setSearchParams] = React.useState(() => createParams(nextSearchParams))
|
||||
const [searchParams, setSearchParams] = React.useState(() => parseSearchParams(nextSearchParams))
|
||||
|
||||
const stringifyParams = React.useCallback(
|
||||
({ params, replace = false }: { params: State; replace?: boolean }) => {
|
||||
({ params, replace = false }: { params: qs.ParsedQs; replace?: boolean }) => {
|
||||
return qs.stringify(
|
||||
{
|
||||
...(replace ? {} : searchParams),
|
||||
@@ -48,10 +45,23 @@ export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
setSearchParams(createParams(nextSearchParams))
|
||||
setSearchParams(parseSearchParams(nextSearchParams))
|
||||
}, [nextSearchParams])
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client'
|
||||
import type { ClientUser, Where } from 'payload'
|
||||
|
||||
import { useSearchParams } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import { useLocale } from '../Locale/index.js'
|
||||
import { useSearchParams } from '../SearchParams/index.js'
|
||||
|
||||
export enum SelectAllStatus {
|
||||
AllAvailable = 'allAvailable',
|
||||
@@ -51,7 +52,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
||||
|
||||
const [selectAll, setSelectAll] = useState<SelectAllStatus>(SelectAllStatus.None)
|
||||
const [count, setCount] = useState(0)
|
||||
const { searchParams } = useSearchParams()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const toggleAll = useCallback(
|
||||
(allAvailable = false) => {
|
||||
@@ -110,7 +111,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
|
||||
let where: Where
|
||||
|
||||
if (selectAll === SelectAllStatus.AllAvailable) {
|
||||
const params = searchParams?.where as Where
|
||||
const params = parseSearchParams(searchParams)?.where as Where
|
||||
|
||||
where = params || {
|
||||
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