feat: extends useSearchParams hook (#5203)
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -2,5 +2,4 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
resetParams: () => void
|
||||
}
|
||||
|
||||
@@ -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'>(
|
||||
|
||||
@@ -11,7 +11,6 @@ export type Props = {
|
||||
handleSortChange?: (sort: string) => void
|
||||
handleWhereChange?: (where: Where) => void
|
||||
modifySearchQuery?: boolean
|
||||
resetParams?: () => void
|
||||
textFieldsToBeSearched?: FieldAffectingData[]
|
||||
titleField: FieldAffectingData
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -2,5 +2,4 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
resetParams: () => void
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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[]>()
|
||||
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
27
packages/ui/src/providers/SearchParams/types.ts
Normal file
27
packages/ui/src/providers/SearchParams/types.ts
Normal 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'
|
||||
}
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user