fix: simplify searchParams provider, leverage next router properly (#5273)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
import * as facelessUIImport from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
@@ -33,7 +34,8 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
const { count, getQueryParams, selectAll, toggleAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasDeletePermission = collectionPermissions?.delete?.permission
|
||||
@@ -60,11 +62,14 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
if (res.status < 400) {
|
||||
toast.success(json.message || t('general:deletedSuccessfully'), { autoClose: 3000 })
|
||||
toggleAll()
|
||||
dispatchSearchParams({
|
||||
type: 'SET',
|
||||
browserHistory: 'replace',
|
||||
params: { page: selectAll ? '1' : undefined },
|
||||
})
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
replace: true,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -81,13 +86,14 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
}, [
|
||||
addDefaultError,
|
||||
api,
|
||||
dispatchSearchParams,
|
||||
getQueryParams,
|
||||
i18n.language,
|
||||
modalSlug,
|
||||
router,
|
||||
selectAll,
|
||||
serverURL,
|
||||
slug,
|
||||
stringifyParams,
|
||||
t,
|
||||
toggleAll,
|
||||
toggleModal,
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { FormState } from 'payload/types'
|
||||
|
||||
import * as facelessUIImport from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
|
||||
import type { Props } from './types.js'
|
||||
@@ -102,7 +103,8 @@ export const EditMany: React.FC<Props> = (props) => {
|
||||
const { count, getQueryParams, selectAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [selected, setSelected] = useState([])
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { componentMap } = useComponentMap()
|
||||
const [reducedFieldMap, setReducedFieldMap] = useState([])
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
@@ -155,11 +157,11 @@ export const EditMany: React.FC<Props> = (props) => {
|
||||
}
|
||||
|
||||
const onSuccess = () => {
|
||||
dispatchSearchParams({
|
||||
type: 'SET',
|
||||
browserHistory: 'replace',
|
||||
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
|
||||
})
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
@@ -21,7 +22,8 @@ const Localizer: React.FC<{
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const { dispatchSearchParams, searchParams } = useSearchParams()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
if (localization) {
|
||||
const { locales } = localization
|
||||
@@ -34,25 +36,21 @@ const Localizer: React.FC<{
|
||||
render={({ close }) => (
|
||||
<PopupList.ButtonGroup>
|
||||
{locales.map((localeOption) => {
|
||||
const newParams = {
|
||||
...searchParams,
|
||||
locale: localeOption.code,
|
||||
}
|
||||
const localeOptionLabel = getTranslation(localeOption.label, i18n)
|
||||
|
||||
return (
|
||||
<PopupList.Button
|
||||
active={locale.code === localeOption.code}
|
||||
href={{ query: newParams }}
|
||||
key={localeOption.code}
|
||||
onClick={() => {
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
locale: localeOption.code,
|
||||
},
|
||||
}),
|
||||
)
|
||||
close()
|
||||
dispatchSearchParams({
|
||||
type: 'SET',
|
||||
params: {
|
||||
locale: searchParams.locale,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
{localeOptionLabel}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
import * as facelessUIImport from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
@@ -33,7 +34,8 @@ export const PublishMany: React.FC<Props> = (props) => {
|
||||
const { i18n, t } = useTranslation()
|
||||
const { getQueryParams, selectAll } = useSelection()
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasPermission = collectionPermissions?.update?.permission
|
||||
@@ -65,11 +67,13 @@ export const PublishMany: React.FC<Props> = (props) => {
|
||||
toggleModal(modalSlug)
|
||||
if (res.status < 400) {
|
||||
toast.success(t('general:updatedSuccessfully'))
|
||||
dispatchSearchParams({
|
||||
type: 'SET',
|
||||
browserHistory: 'replace',
|
||||
params: { page: selectAll ? '1' : undefined },
|
||||
})
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -94,7 +98,6 @@ export const PublishMany: React.FC<Props> = (props) => {
|
||||
slug,
|
||||
t,
|
||||
toggleModal,
|
||||
dispatchSearchParams,
|
||||
])
|
||||
|
||||
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
import * as facelessUIImport from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
@@ -32,7 +33,8 @@ export const UnpublishMany: React.FC<Props> = (props) => {
|
||||
const { i18n, t } = useTranslation()
|
||||
const { getQueryParams, selectAll } = useSelection()
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const { dispatchSearchParams } = useSearchParams()
|
||||
const { stringifyParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasPermission = collectionPermissions?.update?.permission
|
||||
@@ -61,11 +63,13 @@ export const UnpublishMany: React.FC<Props> = (props) => {
|
||||
toggleModal(modalSlug)
|
||||
if (res.status < 400) {
|
||||
toast.success(t('general:updatedSuccessfully'))
|
||||
dispatchSearchParams({
|
||||
type: 'SET',
|
||||
browserHistory: 'replace',
|
||||
params: { page: selectAll ? '1' : undefined },
|
||||
})
|
||||
router.replace(
|
||||
stringifyParams({
|
||||
params: {
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -82,7 +86,6 @@ export const UnpublishMany: React.FC<Props> = (props) => {
|
||||
}, [
|
||||
addDefaultError,
|
||||
api,
|
||||
dispatchSearchParams,
|
||||
getQueryParams,
|
||||
i18n.language,
|
||||
modalSlug,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import type { Locale } from 'payload/config'
|
||||
|
||||
// TODO: abstract the `next/navigation` dependency out from this component
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { findLocaleFromCode } from '../../utilities/findLocaleFromCode.js'
|
||||
@@ -18,15 +16,14 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
|
||||
const { localization } = useConfig()
|
||||
|
||||
const { user } = useAuth()
|
||||
|
||||
const defaultLocale =
|
||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||
|
||||
const { dispatchSearchParams, searchParams } = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { searchParams } = useSearchParams()
|
||||
const localeFromParams = searchParams?.locale || ''
|
||||
|
||||
const [localeCode, setLocaleCode] = useState<string>(
|
||||
(searchParams?.locale as string) || defaultLocale,
|
||||
(localeFromParams as string) || defaultLocale,
|
||||
)
|
||||
|
||||
const [locale, setLocale] = useState<Locale | null>(
|
||||
@@ -35,53 +32,55 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
|
||||
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
|
||||
const localeFromParams = searchParams.locale
|
||||
|
||||
useEffect(() => {
|
||||
async function localeChangeHandler() {
|
||||
const switchLocale = React.useCallback(
|
||||
async (newLocale: string) => {
|
||||
if (!localization) {
|
||||
return
|
||||
}
|
||||
|
||||
// set locale from search param
|
||||
if (localeFromParams && localization.localeCodes.indexOf(localeFromParams as string) > -1) {
|
||||
setLocaleCode(localeFromParams as string)
|
||||
setLocale(findLocaleFromCode(localization, localeFromParams as string))
|
||||
if (user) await setPreference('locale', localeFromParams)
|
||||
return
|
||||
}
|
||||
const localeToSet =
|
||||
localization.localeCodes.indexOf(newLocale) > -1 ? newLocale : defaultLocale
|
||||
|
||||
// set locale from preferences or default
|
||||
let preferenceLocale: string
|
||||
let isPreferenceInConfig: boolean
|
||||
if (user) {
|
||||
preferenceLocale = await getPreference<string>('locale')
|
||||
isPreferenceInConfig =
|
||||
preferenceLocale && localization.localeCodes.indexOf(preferenceLocale) > -1
|
||||
if (isPreferenceInConfig) {
|
||||
setLocaleCode(preferenceLocale)
|
||||
setLocale(findLocaleFromCode(localization, preferenceLocale))
|
||||
return
|
||||
if (localeToSet !== localeCode) {
|
||||
setLocaleCode(localeToSet)
|
||||
setLocale(findLocaleFromCode(localization, localeToSet))
|
||||
try {
|
||||
if (user) await setPreference('locale', localeToSet)
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
}
|
||||
await setPreference('locale', defaultLocale)
|
||||
}
|
||||
setLocaleCode(defaultLocale)
|
||||
setLocale(findLocaleFromCode(localization, defaultLocale))
|
||||
}
|
||||
|
||||
void localeChangeHandler()
|
||||
}, [defaultLocale, getPreference, localeFromParams, setPreference, user, localization, router])
|
||||
},
|
||||
[localization, setPreference, user, defaultLocale, localeCode],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams?.locale) {
|
||||
dispatchSearchParams({
|
||||
type: 'SET',
|
||||
params: {
|
||||
locale: searchParams.locale,
|
||||
},
|
||||
})
|
||||
async function setInitialLocale() {
|
||||
let localeToSet = defaultLocale
|
||||
|
||||
if (typeof localeFromParams === 'string') {
|
||||
localeToSet = localeFromParams
|
||||
} else if (user) {
|
||||
try {
|
||||
localeToSet = await getPreference<string>('locale')
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
}
|
||||
}
|
||||
|
||||
await switchLocale(localeToSet)
|
||||
}
|
||||
}, [searchParams.locale, dispatchSearchParams])
|
||||
|
||||
void setInitialLocale()
|
||||
}, [
|
||||
defaultLocale,
|
||||
getPreference,
|
||||
localization,
|
||||
localeFromParams,
|
||||
setPreference,
|
||||
user,
|
||||
switchLocale,
|
||||
])
|
||||
|
||||
return <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
import { useSearchParams as useNextSearchParams, useRouter } from 'next/navigation.js'
|
||||
import { useSearchParams as useNextSearchParams } from 'next/navigation.js'
|
||||
import qs from 'qs'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
import type { Action, SearchParamsContext, State } from './types.js'
|
||||
import type { SearchParamsContext, State } from './types.js'
|
||||
|
||||
const initialContext: SearchParamsContext = {
|
||||
dispatchSearchParams: () => {},
|
||||
searchParams: {},
|
||||
stringifyParams: () => '',
|
||||
}
|
||||
|
||||
const Context = createContext(initialContext)
|
||||
@@ -15,46 +15,36 @@ const Context = createContext(initialContext)
|
||||
// TODO: abstract the `next/navigation` dependency out from this provider so that it can be used in other contexts
|
||||
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const nextSearchParams = useNextSearchParams()
|
||||
const router = useRouter()
|
||||
const initialParams = qs.parse(nextSearchParams.toString(), {
|
||||
const searchString = nextSearchParams.toString()
|
||||
const initialParams = qs.parse(searchString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
|
||||
const [searchParams, dispatchSearchParams] = React.useReducer((state: State, action: Action) => {
|
||||
const stackAction = action.browserHistory || 'push'
|
||||
let paramsToSet
|
||||
const [searchParams, setSearchParams] = React.useState(initialParams)
|
||||
|
||||
switch (action.type) {
|
||||
case 'SET':
|
||||
paramsToSet = {
|
||||
...state,
|
||||
...action.params,
|
||||
}
|
||||
break
|
||||
case 'REPLACE':
|
||||
paramsToSet = action.params
|
||||
break
|
||||
case 'CLEAR':
|
||||
paramsToSet = {}
|
||||
break
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
||||
const newSearchString = qs.stringify(paramsToSet, { addQueryPrefix: true })
|
||||
if (stackAction === 'push') {
|
||||
router.push(newSearchString)
|
||||
} else if (stackAction === 'replace') {
|
||||
router.replace(newSearchString)
|
||||
}
|
||||
|
||||
return paramsToSet
|
||||
}, initialParams)
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ dispatchSearchParams, searchParams }}>{children}</Context.Provider>
|
||||
const stringifyParams = React.useCallback(
|
||||
({ params, replace = false }: { params: State; replace?: boolean }) => {
|
||||
return qs.stringify(
|
||||
{
|
||||
...(replace ? {} : searchParams),
|
||||
...params,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)
|
||||
},
|
||||
[searchParams],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
const newSearchParams = qs.parse(searchString, {
|
||||
depth: 10,
|
||||
ignoreQueryPrefix: true,
|
||||
})
|
||||
setSearchParams(newSearchParams)
|
||||
}, [searchString])
|
||||
|
||||
return <Context.Provider value={{ searchParams, stringifyParams }}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
export const useSearchParams = (): SearchParamsContext => useContext(Context)
|
||||
|
||||
@@ -1,27 +1,6 @@
|
||||
export type SearchParamsContext = {
|
||||
dispatchSearchParams: (action: Action) => void
|
||||
searchParams: qs.ParsedQs
|
||||
stringifyParams: ({ params, replace }: { params: State; replace?: boolean }) => string
|
||||
}
|
||||
|
||||
export type State = qs.ParsedQs
|
||||
|
||||
export type Action = (
|
||||
| {
|
||||
params: qs.ParsedQs
|
||||
type: 'REPLACE'
|
||||
}
|
||||
| {
|
||||
params: qs.ParsedQs
|
||||
type: 'SET'
|
||||
}
|
||||
| {
|
||||
type: 'CLEAR'
|
||||
}
|
||||
) & {
|
||||
/**
|
||||
* `push` will add a new entry to the browser history stack.
|
||||
* `replace` will overwrite the browser history entry.
|
||||
* @default 'push'
|
||||
* */
|
||||
browserHistory?: 'push' | 'replace'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user