feat: extends useSearchParams hook (#5203)

This commit is contained in:
Jarrod Flesch
2024-02-28 09:33:59 -05:00
committed by GitHub
parent 96349be350
commit 170ae3602b
18 changed files with 147 additions and 69 deletions

View File

@@ -7,12 +7,12 @@ import type { Props } from './types'
import Form from '../../forms/Form'
import { useForm } from '../../forms/Form/context'
import RenderFields from '../../forms/RenderFields'
import FormSubmit from '../../forms/Submit'
import { X } from '../../icons/X'
import { useAuth } from '../../providers/Auth'
import { useConfig } from '../../providers/Config'
import { OperationContext } from '../../providers/OperationProvider'
import { useSearchParams } from '../../providers/SearchParams'
import { SelectAllStatus, useSelection } from '../../providers/SelectionProvider'
import { useTranslation } from '../../providers/Translation'
import { Drawer, DrawerToggler } from '../Drawer'
@@ -82,7 +82,7 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
)
}
const EditMany: React.FC<Props> = (props) => {
const { collection: { fields, labels: { plural }, slug } = {}, collection, resetParams } = props
const { collection: { slug, fields, labels: { plural } } = {}, collection } = props
const { permissions } = useAuth()
const { closeModal } = useModal()
@@ -93,6 +93,7 @@ const EditMany: React.FC<Props> = (props) => {
const { count, getQueryParams, selectAll } = useSelection()
const { i18n, t } = useTranslation()
const [selected, setSelected] = useState([])
const { dispatchSearchParams } = useSearchParams()
const collectionPermissions = permissions?.collections?.[slug]
const hasUpdatePermission = collectionPermissions?.update?.permission
@@ -104,7 +105,11 @@ const EditMany: React.FC<Props> = (props) => {
}
const onSuccess = () => {
resetParams({ page: selectAll === SelectAllStatus.AllAvailable ? 1 : undefined })
dispatchSearchParams({
type: 'set',
browserHistory: 'replace',
params: { page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined },
})
}
return (

View File

@@ -2,5 +2,4 @@ import type { SanitizedCollectionConfig } from 'payload/types'
export type Props = {
collection: SanitizedCollectionConfig
resetParams: () => void
}

View File

@@ -40,12 +40,11 @@ export const ListControls: React.FC<Props> = (props) => {
handleSortChange,
handleWhereChange,
modifySearchQuery = true,
resetParams,
textFieldsToBeSearched,
titleField,
} = props
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const shouldInitializeWhereOpened = validateWhereQuery(searchParams?.where)
const [visibleDrawer, setVisibleDrawer] = useState<'columns' | 'sort' | 'where'>(

View File

@@ -11,7 +11,6 @@ export type Props = {
handleSortChange?: (sort: string) => void
handleWhereChange?: (where: Where) => void
modifySearchQuery?: boolean
resetParams?: () => void
textFieldsToBeSearched?: FieldAffectingData[]
titleField: FieldAffectingData
}

View File

@@ -22,7 +22,7 @@ const Localizer: React.FC<{
const { i18n } = useTranslation()
const locale = useLocale()
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const localeLabel = getTranslation(locale.label, i18n)

View File

@@ -21,7 +21,7 @@ const baseClass = 'paginator'
export const Pagination: React.FC<Props> = (props) => {
const router = useRouter()
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const pathname = usePathname()
const {

View File

@@ -30,7 +30,7 @@ export const PerPage: React.FC<Props> = ({
modifySearchParams = true,
resetPage = false,
}) => {
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const history = useHistory()
const { t } = useTranslation()

View File

@@ -8,10 +8,11 @@ import type { Props } from './types'
import { useAuth } from '../../providers/Auth'
import { useConfig } from '../../providers/Config'
import { useSearchParams } from '../../providers/SearchParams'
import { SelectAllStatus, useSelection } from '../../providers/SelectionProvider'
import { useTranslation } from '../../providers/Translation'
// import { requests } from '../../../api'
import { MinimalTemplate } from '../../templates/Minimal'
import { requests } from '../../utilities/api'
import { Button } from '../Button'
import Pill from '../Pill'
import './index.scss'
@@ -19,7 +20,7 @@ import './index.scss'
const baseClass = 'publish-many'
const PublishMany: React.FC<Props> = (props) => {
const { collection: { labels: { plural }, slug, versions } = {}, resetParams } = props
const { collection: { slug, labels: { plural }, versions } = {} } = props
const {
routes: { api },
@@ -28,8 +29,9 @@ const PublishMany: React.FC<Props> = (props) => {
const { permissions } = useAuth()
const { toggleModal } = useModal()
const { i18n, t } = useTranslation()
const { count, getQueryParams, selectAll } = useSelection()
const { getQueryParams, selectAll } = useSelection()
const [submitted, setSubmitted] = useState(false)
const { dispatchSearchParams } = useSearchParams()
const collectionPermissions = permissions?.collections?.[slug]
const hasPermission = collectionPermissions?.update?.permission
@@ -40,53 +42,57 @@ const PublishMany: React.FC<Props> = (props) => {
toast.error(t('error:unknown'))
}, [t])
const handlePublish = useCallback(() => {
const handlePublish = useCallback(async () => {
setSubmitted(true)
// requests
// .patch(
// `${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'published' } })}`,
// {
// body: JSON.stringify({
// _status: 'published',
// }),
// headers: {
// 'Accept-Language': i18n.language,
// 'Content-Type': 'application/json',
// },
// },
// )
// .then(async (res) => {
// try {
// const json = await res.json()
// toggleModal(modalSlug)
// if (res.status < 400) {
// toast.success(t('general:updatedSuccessfully'))
// resetParams({ page: selectAll ? 1 : undefined })
// return null
// }
await requests
.patch(
`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'published' } })}`,
{
body: JSON.stringify({
_status: 'published',
}),
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
},
},
)
.then(async (res) => {
try {
const json = await res.json()
toggleModal(modalSlug)
if (res.status < 400) {
toast.success(t('general:updatedSuccessfully'))
dispatchSearchParams({
type: 'set',
browserHistory: 'replace',
params: { page: selectAll ? '1' : undefined },
})
return null
}
// if (json.errors) {
// json.errors.forEach((error) => toast.error(error.message))
// } else {
// addDefaultError()
// }
// return false
// } catch (e) {
// return addDefaultError()
// }
// })
if (json.errors) {
json.errors.forEach((error) => toast.error(error.message))
} else {
addDefaultError()
}
return false
} catch (e) {
return addDefaultError()
}
})
}, [
addDefaultError,
api,
getQueryParams,
i18n.language,
modalSlug,
resetParams,
selectAll,
serverURL,
slug,
t,
toggleModal,
dispatchSearchParams,
])
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {

View File

@@ -2,5 +2,4 @@ import type { SanitizedCollectionConfig } from 'payload/types'
export type Props = {
collection: SanitizedCollectionConfig
resetParams: () => void
}

View File

@@ -22,7 +22,7 @@ const SearchFilter: React.FC<Props> = (props) => {
modifySearchQuery = true,
} = props
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const history = useHistory()
const { i18n, t } = useTranslation()

View File

@@ -15,7 +15,7 @@ const baseClass = 'sort-column'
export const SortColumn: React.FC<Props> = (props) => {
const { name, disable = false, label } = props
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const history = useHistory()
const { i18n, t } = useTranslation()

View File

@@ -20,7 +20,7 @@ const SortComplex: React.FC<Props> = (props) => {
const { collection, handleChange, modifySearchQuery = true } = props
const history = useHistory()
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const { i18n, t } = useTranslation()
const [sortOptions, setSortOptions] = useState<OptionObject[]>()

View File

@@ -66,7 +66,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
const collection = config.collections.find((c) => c.slug === collectionSlug)
const [reducedFields] = useState(() => reduceFields(collection.fields, i18n))
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
// This handles initializing the where conditions from the search query (URL). That way, if you pass in
// query params to the URL, the where conditions will be initialized from those and displayed in the UI.
@@ -187,7 +187,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
iconStyle="with-border"
onClick={() => {
if (reducedFields.length > 0)
dispatchConditions({ field: reducedFields[0].value, type: 'add' })
dispatchConditions({ type: 'add', field: reducedFields[0].value })
}}
>
{t('general:or')}
@@ -205,7 +205,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
iconStyle="with-border"
onClick={() => {
if (reducedFields.length > 0)
dispatchConditions({ field: reducedFields[0].value, type: 'add' })
dispatchConditions({ type: 'add', field: reducedFields[0].value })
}}
>
{t('general:addFilter')}

View File

@@ -2,7 +2,7 @@
import type { Permissions, User } from 'payload/auth'
import { useModal } from '@faceless-ui/modal'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { usePathname, useRouter } from 'next/navigation'
import qs from 'qs'
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
import { toast } from 'react-toastify'
@@ -13,6 +13,7 @@ import useDebounce from '../../hooks/useDebounce'
import { useTranslation } from '../../providers/Translation'
import { requests } from '../../utilities/api'
import { useConfig } from '../Config'
import { useSearchParams } from '../SearchParams'
// import { useLocale } from '../Locale'
const Context = createContext({} as AuthContext)
@@ -20,7 +21,7 @@ const Context = createContext({} as AuthContext)
const maxTimeoutTime = 2147483647
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const [user, setUser] = useState<User | null>()
const [tokenInMemory, setTokenInMemory] = useState<string>()
const [tokenExpiration, setTokenExpiration] = useState<number>()

View File

@@ -20,7 +20,7 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child
const defaultLocale =
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
const searchParams = useSearchParams()
const { searchParams } = useSearchParams()
const [localeCode, setLocaleCode] = useState<string>(
(searchParams?.locale as string) || defaultLocale,

View File

@@ -1,17 +1,59 @@
'use client'
import { useSearchParams as useNextSearchParams } from 'next/navigation'
import { useSearchParams as useNextSearchParams, useRouter } from 'next/navigation'
import qs from 'qs'
import React, { createContext, useContext } from 'react'
interface ISearchParamsContext extends qs.ParsedQs {}
import type { Action, SearchParamsContext, State } from './types'
const Context = createContext<ISearchParamsContext>({} as ISearchParamsContext)
const initialContext: SearchParamsContext = {
dispatchSearchParams: () => {},
searchParams: {},
}
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 searchParams = qs.parse(nextSearchParams.toString(), { depth: 10, ignoreQueryPrefix: true })
return <Context.Provider value={searchParams}>{children}</Context.Provider>
const router = useRouter()
const initialParams = qs.parse(nextSearchParams.toString(), {
depth: 10,
ignoreQueryPrefix: true,
})
const [searchParams, dispatchSearchParams] = React.useReducer((state: State, action: Action) => {
const stackAction = action.browserHistory || 'push'
let paramsToSet
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>
)
}
export const useSearchParams = (): ISearchParamsContext => useContext(Context)
export const useSearchParams = (): SearchParamsContext => useContext(Context)

View File

@@ -0,0 +1,27 @@
export type SearchParamsContext = {
dispatchSearchParams: (action: Action) => void
searchParams: qs.ParsedQs
}
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'
}

View File

@@ -5,6 +5,7 @@ import queryString from 'qs'
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { useLocale } from '../Locale'
import { useSearchParams } from '../SearchParams'
export enum SelectAllStatus {
AllAvailable = 'allAvailable',
@@ -37,6 +38,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
const [selected, setSelected] = useState<SelectionContext['selected']>({})
const [selectAll, setSelectAll] = useState<SelectAllStatus>(SelectAllStatus.None)
const [count, setCount] = useState(0)
const { searchParams } = useSearchParams()
const toggleAll = useCallback(
(allAvailable = false) => {
@@ -83,11 +85,10 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
(additionalParams?: Where): string => {
let where: Where
if (selectAll === SelectAllStatus.AllAvailable) {
// const params = queryString.parse(history.location.search, { ignoreQueryPrefix: true })
// .where as Where
// where = params || {
// id: { not_equals: '' },
// }
const params = searchParams?.where as Where
where = params || {
id: { not_equals: '' },
}
} else {
where = {
id: {
@@ -110,7 +111,7 @@ export const SelectionProvider: React.FC<Props> = ({ children, docs = [], totalD
{ addQueryPrefix: true },
)
},
[selectAll, selected, locale],
[selectAll, selected, locale, searchParams],
)
useEffect(() => {