Skip to content

Commit edc04ae

Browse files
authored
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_
1 parent d04cea1 commit edc04ae

File tree

13 files changed

+142
-89
lines changed

13 files changed

+142
-89
lines changed

packages/next/src/elements/DocumentHeader/Tabs/Tab/TabLink.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
'use client'
22
import type { SanitizedConfig } from 'payload'
33

4-
import { useSearchParams } from '@payloadcms/ui'
54
import { formatAdminURL } from '@payloadcms/ui/shared'
65
import LinkImport from 'next/link.js'
7-
import { useParams, usePathname } from 'next/navigation.js'
6+
import { useParams, usePathname, useSearchParams } from 'next/navigation.js'
87
import React from 'react'
98

109
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
@@ -30,12 +29,9 @@ export const DocumentTabLink: React.FC<{
3029
const pathname = usePathname()
3130
const params = useParams()
3231

33-
const { searchParams } = useSearchParams()
32+
const searchParams = useSearchParams()
3433

35-
const locale =
36-
'locale' in searchParams && typeof searchParams.locale === 'string'
37-
? searchParams.locale
38-
: undefined
34+
const locale = searchParams.get('locale')
3935

4036
const [entityType, entitySlug, segmentThree, segmentFour, ...rest] = params.segments || []
4137
const isCollection = entityType === 'collections'

packages/ui/src/elements/DeleteMany/index.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import type { ClientCollectionConfig } from 'payload'
33

44
import { Modal, useModal } from '@faceless-ui/modal'
55
import { getTranslation } from '@payloadcms/translations'
6-
import { useRouter } from 'next/navigation.js'
6+
import { useRouter, useSearchParams } from 'next/navigation.js'
7+
import * as qs from 'qs-esm'
78
import React, { useCallback, useState } from 'react'
89
import { toast } from 'sonner'
910

1011
import { useAuth } from '../../providers/Auth/index.js'
1112
import { useConfig } from '../../providers/Config/index.js'
1213
import { useRouteCache } from '../../providers/RouteCache/index.js'
13-
import { useSearchParams } from '../../providers/SearchParams/index.js'
1414
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
1515
import { useTranslation } from '../../providers/Translation/index.js'
1616
import { requests } from '../../utilities/api.js'
@@ -41,7 +41,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
4141
const { i18n, t } = useTranslation()
4242
const [deleting, setDeleting] = useState(false)
4343
const router = useRouter()
44-
const { searchParams, stringifyParams } = useSearchParams()
44+
const searchParams = useSearchParams()
4545
const { clearRouteCache } = useRouteCache()
4646

4747
const collectionPermissions = permissions?.collections?.[slug]
@@ -58,7 +58,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
5858

5959
const queryWithSearch = mergeListSearchAndWhere({
6060
collectionConfig: collection,
61-
search: searchParams?.search as string,
61+
search: searchParams.get('search'),
6262
})
6363

6464
const queryString = getQueryParams(queryWithSearch)
@@ -85,21 +85,26 @@ export const DeleteMany: React.FC<Props> = (props) => {
8585
label: getTranslation(successLabel, i18n),
8686
}),
8787
)
88+
8889
if (json?.errors.length > 0) {
8990
toast.error(json.message, {
9091
description: json.errors.map((error) => error.message).join('\n'),
9192
})
9293
}
94+
9395
toggleAll()
96+
9497
router.replace(
95-
stringifyParams({
96-
params: {
98+
qs.stringify(
99+
{
97100
page: selectAll ? '1' : undefined,
98101
},
99-
replace: true,
100-
}),
102+
{ addQueryPrefix: true },
103+
),
101104
)
105+
102106
clearRouteCache()
107+
103108
return null
104109
}
105110

@@ -111,7 +116,7 @@ export const DeleteMany: React.FC<Props> = (props) => {
111116
addDefaultError()
112117
}
113118
return false
114-
} catch (e) {
119+
} catch (_err) {
115120
return addDefaultError()
116121
}
117122
})
@@ -128,7 +133,6 @@ export const DeleteMany: React.FC<Props> = (props) => {
128133
serverURL,
129134
singular,
130135
slug,
131-
stringifyParams,
132136
t,
133137
toggleAll,
134138
toggleModal,

packages/ui/src/elements/EditMany/index.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type { ClientCollectionConfig, FormState } from 'payload'
33

44
import { useModal } from '@faceless-ui/modal'
55
import { getTranslation } from '@payloadcms/translations'
6-
import { useRouter } from 'next/navigation.js'
6+
import { useRouter, useSearchParams } from 'next/navigation.js'
7+
import * as qs from 'qs-esm'
78
import React, { useCallback, useEffect, useMemo, useState } from 'react'
89

910
import type { FormProps } from '../../forms/Form/index.js'
@@ -19,14 +20,14 @@ import { DocumentInfoProvider } from '../../providers/DocumentInfo/index.js'
1920
import { EditDepthProvider } from '../../providers/EditDepth/index.js'
2021
import { OperationContext } from '../../providers/Operation/index.js'
2122
import { useRouteCache } from '../../providers/RouteCache/index.js'
22-
import { useSearchParams } from '../../providers/SearchParams/index.js'
2323
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
2424
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
2525
import { useTranslation } from '../../providers/Translation/index.js'
2626
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
2727
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
28-
import { Drawer, DrawerToggler } from '../Drawer/index.js'
28+
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
2929
import './index.scss'
30+
import { Drawer, DrawerToggler } from '../Drawer/index.js'
3031
import { FieldSelect } from '../FieldSelect/index.js'
3132

3233
const baseClass = 'edit-many'
@@ -125,7 +126,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
125126
const { count, getQueryParams, selectAll } = useSelection()
126127
const { i18n, t } = useTranslation()
127128
const [selected, setSelected] = useState([])
128-
const { searchParams, stringifyParams } = useSearchParams()
129+
const searchParams = useSearchParams()
129130
const router = useRouter()
130131
const [initialState, setInitialState] = useState<FormState>()
131132
const hasInitializedState = React.useRef(false)
@@ -195,17 +196,21 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
195196
const queryString = useMemo(() => {
196197
const queryWithSearch = mergeListSearchAndWhere({
197198
collectionConfig: collection,
198-
search: searchParams?.search as string,
199+
search: searchParams.get('search'),
199200
})
200201

201202
return getQueryParams(queryWithSearch)
202203
}, [collection, searchParams, getQueryParams])
203204

204205
const onSuccess = () => {
205206
router.replace(
206-
stringifyParams({
207-
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
208-
}),
207+
qs.stringify(
208+
{
209+
...parseSearchParams(searchParams),
210+
page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined,
211+
},
212+
{ addQueryPrefix: true },
213+
),
209214
)
210215
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
211216
closeModal(drawerSlug)

packages/ui/src/elements/Localizer/index.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
'use client'
22
import { getTranslation } from '@payloadcms/translations'
3+
import { useSearchParams } from 'next/navigation.js'
4+
import * as qs from 'qs-esm'
35
import React from 'react'
46

57
import { useConfig } from '../../providers/Config/index.js'
68
import { useLocale } from '../../providers/Locale/index.js'
7-
import { useSearchParams } from '../../providers/SearchParams/index.js'
89
import { useTranslation } from '../../providers/Translation/index.js'
10+
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
911
import { Popup, PopupList } from '../Popup/index.js'
10-
import './index.scss'
1112
import { LocalizerLabel } from './LocalizerLabel/index.js'
13+
import './index.scss'
1214

1315
const baseClass = 'localizer'
1416

@@ -18,10 +20,10 @@ export const Localizer: React.FC<{
1820
const { className } = props
1921
const { config } = useConfig()
2022
const { localization } = config
23+
const searchParams = useSearchParams()
2124

2225
const { i18n } = useTranslation()
2326
const locale = useLocale()
24-
const { stringifyParams } = useSearchParams()
2527

2628
if (localization) {
2729
const { locales } = localization
@@ -39,11 +41,13 @@ export const Localizer: React.FC<{
3941
return (
4042
<PopupList.Button
4143
active={locale.code === localeOption.code}
42-
href={stringifyParams({
43-
params: {
44+
href={qs.stringify(
45+
{
46+
...parseSearchParams(searchParams),
4447
locale: localeOption.code,
4548
},
46-
})}
49+
{ addQueryPrefix: true },
50+
)}
4751
key={localeOption.code}
4852
onClick={close}
4953
>

packages/ui/src/elements/PublishMany/index.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ import type { ClientCollectionConfig } from 'payload'
33

44
import { Modal, useModal } from '@faceless-ui/modal'
55
import { getTranslation } from '@payloadcms/translations'
6-
import { useRouter } from 'next/navigation.js'
6+
import { useRouter, useSearchParams } from 'next/navigation.js'
7+
import * as qs from 'qs-esm'
78
import React, { useCallback, useState } from 'react'
89
import { toast } from 'sonner'
910

1011
import { useAuth } from '../../providers/Auth/index.js'
1112
import { useConfig } from '../../providers/Config/index.js'
1213
import { useRouteCache } from '../../providers/RouteCache/index.js'
13-
import { useSearchParams } from '../../providers/SearchParams/index.js'
1414
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
1515
import { useTranslation } from '../../providers/Translation/index.js'
1616
import { requests } from '../../utilities/api.js'
17+
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
1718
import { Button } from '../Button/index.js'
1819
import { Pill } from '../Pill/index.js'
1920
import './index.scss'
@@ -41,7 +42,7 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
4142
const { getQueryParams, selectAll } = useSelection()
4243
const [submitted, setSubmitted] = useState(false)
4344
const router = useRouter()
44-
const { stringifyParams } = useSearchParams()
45+
const searchParams = useSearchParams()
4546

4647
const collectionPermissions = permissions?.collections?.[slug]
4748
const hasPermission = collectionPermissions?.update
@@ -82,17 +83,21 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
8283
label: getTranslation(successLabel, i18n),
8384
}),
8485
)
86+
8587
if (json?.errors.length > 0) {
8688
toast.error(json.message, {
8789
description: json.errors.map((error) => error.message).join('\n'),
8890
})
8991
}
92+
9093
router.replace(
91-
stringifyParams({
92-
params: {
94+
qs.stringify(
95+
{
96+
...parseSearchParams(searchParams),
9397
page: selectAll ? '1' : undefined,
9498
},
95-
}),
99+
{ addQueryPrefix: true },
100+
),
96101
)
97102

98103
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) => {
110115
}
111116
})
112117
}, [
113-
addDefaultError,
118+
serverURL,
114119
api,
120+
slug,
115121
getQueryParams,
116122
i18n,
123+
toggleModal,
117124
modalSlug,
118125
plural,
119-
selectAll,
120-
serverURL,
121126
singular,
122-
slug,
123127
t,
124-
toggleModal,
125128
router,
126-
stringifyParams,
129+
searchParams,
130+
selectAll,
127131
clearRouteCache,
132+
addDefaultError,
128133
])
129134

130135
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {

packages/ui/src/elements/SortComplex/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { OptionObject, SanitizedCollectionConfig } from 'payload'
33

44
import { getTranslation } from '@payloadcms/translations'
55
// TODO: abstract the `next/navigation` dependency out from this component
6-
import { usePathname, useRouter } from 'next/navigation.js'
6+
import { usePathname, useRouter, useSearchParams } from 'next/navigation.js'
77
import { sortableFieldTypes } from 'payload'
88
import { fieldAffectsData } from 'payload/shared'
99
import * as qs from 'qs-esm'
@@ -18,7 +18,6 @@ export type SortComplexProps = {
1818

1919
import type { Option } from '../ReactSelect/index.js'
2020

21-
import { useSearchParams } from '../../providers/SearchParams/index.js'
2221
import { useTranslation } from '../../providers/Translation/index.js'
2322
import { ReactSelect } from '../ReactSelect/index.js'
2423
import './index.scss'
@@ -30,7 +29,7 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
3029

3130
const router = useRouter()
3231
const pathname = usePathname()
33-
const { searchParams } = useSearchParams()
32+
const searchParams = useSearchParams()
3433
const { i18n, t } = useTranslation()
3534
const [sortOptions, setSortOptions] = useState<OptionObject[]>()
3635

@@ -58,7 +57,7 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
5857
handleChange(newSortValue)
5958
}
6059

61-
if (searchParams.sort !== newSortValue && modifySearchQuery) {
60+
if (searchParams.get('sort') !== newSortValue && modifySearchQuery) {
6261
const search = qs.stringify(
6362
{
6463
...searchParams,

0 commit comments

Comments
 (0)