Skip to content

feat: replace qs with picoquery across codebase #6954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/queries/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@ Simple queries are fairly straightforward to write. To understand the syntax, yo

**`https://localhost:3000/api/posts?where[color][equals]=mint`**

This one isn't too bad, but more complex queries get unavoidably more difficult to write as query strings. For this reason, we recommend to use the extremely helpful and ubiquitous [`qs`](https://www.npmjs.com/package/qs) package to parse your JSON / object-formatted queries into query strings for use with the REST API.
This one isn't too bad, but more complex queries get unavoidably more difficult to write as query strings. For this reason, we recommend to use the extremely helpful and ubiquitous [`picoquery`](https://www.npmjs.com/package/picoquery) package to parse your JSON / object-formatted queries into query strings for use with the REST API.

**For example, using fetch:**

```js
import qs from 'qs'
import { stringify } from 'picoquery'

const query = {
color: {
Expand All @@ -165,14 +165,14 @@ const query = {
}

const getPosts = async () => {
const stringifiedQuery = qs.stringify(
const stringifiedQuery = stringify(
{
where: query, // ensure that `qs` adds the `where` property, too!
where: query, // ensure that the `where` property is added, too!
},
{ addQueryPrefix: true },
{ nestingSyntax: 'index' },
)

const response = await fetch(`http://localhost:3000/api/posts${stringifiedQuery}`)
const response = await fetch(`http://localhost:3000/api/posts?${stringifiedQuery}`)
// Continue to handle the response below...
}
```
Expand Down
6 changes: 4 additions & 2 deletions docs/rest-api/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ Here is an example of how to use the method override to perform a GET request:
#### Using Method Override (POST)

```ts
import { stringify } from 'picoquery'

const res = await fetch(`${api}/${collectionSlug}`, {
method: 'POST',
credentials: 'include',
Expand All @@ -642,10 +644,10 @@ const res = await fetch(`${api}/${collectionSlug}`, {
'Content-Type': 'application/x-www-form-urlencoded',
'X-HTTP-Method-Override': 'GET',
},
body: qs.stringify({
body: stringify({
depth: 1,
locale: 'en',
}),
}, { nestingSyntax: 'index' }),
})
```

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@
"@types/minimist": "1.2.5",
"@types/node": "20.12.5",
"@types/prompts": "^2.4.5",
"@types/qs": "6.9.14",
"@types/react": "npm:[email protected]",
"@types/react-dom": "npm:[email protected]",
"@types/semver": "^7.5.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"graphql-playground-html": "1.6.30",
"http-status": "1.6.2",
"path-to-regexp": "^6.2.1",
"qs": "6.12.1",
"picoquery": "2.3.1",
"react-diff-viewer-continued": "3.2.6",
"sass": "1.77.4",
"sonner": "^1.5.0",
Expand Down
8 changes: 3 additions & 5 deletions packages/next/src/utilities/createPayloadRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CustomPayloadRequestProperties, PayloadRequest, SanitizedConfig }

import { initI18n } from '@payloadcms/translations'
import { executeAuthStrategies, getDataLoader, parseCookies } from 'payload'
import qs from 'qs'
import { parse } from 'picoquery'
import { URL } from 'url'

import { sanitizeLocales } from './addLocalesToRequest.js'
Expand Down Expand Up @@ -61,10 +61,8 @@ export const createPayloadRequest = async ({
const queryToParse = overrideHttpMethod === 'GET' ? await request.text() : urlProperties.search

const query = queryToParse
? qs.parse(queryToParse, {
arrayLimit: 1000,
depth: 10,
ignoreQueryPrefix: true,
? parse(queryToParse, {
nestingSyntax: 'index',
})
: {}

Expand Down
12 changes: 7 additions & 5 deletions packages/next/src/utilities/initPage/handleAuthRedirect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { redirect } from 'next/navigation.js'
import qs from 'qs'
import { parse, stringify } from 'picoquery'

import { isAdminAuthRoute, isAdminRoute } from './shared.js'

Expand All @@ -26,7 +26,7 @@ export const handleAuthRedirect = ({

const redirectRoute = encodeURIComponent(
route + Object.keys(searchParams ?? {}).length
? `${qs.stringify(searchParams, { addQueryPrefix: true })}`
? `?${stringify(searchParams, { nestingSyntax: 'index' })}`
: undefined,
)

Expand All @@ -39,14 +39,16 @@ export const handleAuthRedirect = ({
? adminLoginRoute
: customLoginRoute || loginRouteFromConfig

const parsedLoginRouteSearchParams = qs.parse(loginRoute.split('?')[1] ?? '')
const parsedLoginRouteSearchParams = parse(loginRoute.split('?')[1] ?? '', {
nestingSyntax: 'index',
})

const searchParamsWithRedirect = `${qs.stringify(
const searchParamsWithRedirect = `?${stringify(
{
...parsedLoginRouteSearchParams,
...(redirectRoute ? { redirect: redirectRoute } : {}),
},
{ addQueryPrefix: true },
{ nestingSyntax: 'index' },
)}`

redirect(`${loginRoute.split('?')[0]}${searchParamsWithRedirect}`)
Expand Down
11 changes: 5 additions & 6 deletions packages/next/src/utilities/initPage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { initI18n } from '@payloadcms/translations'
import { findLocaleFromCode } from '@payloadcms/ui/shared'
import { headers as getHeaders } from 'next/headers.js'
import { createLocalReq, isEntityHidden, parseCookies } from 'payload'
import qs from 'qs'
import { parse, stringify } from 'picoquery'

import type { Args } from './types.js'

Expand All @@ -31,7 +31,7 @@ export const initPage = async ({
routes: { admin: adminRoute },
} = payload.config

const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
const queryString = `${stringify(searchParams ?? {}, { nestingSyntax: 'index' })}`
const cookies = parseCookies(headers)
const language = getRequestLanguage({ config: payload.config, cookies, headers })

Expand Down Expand Up @@ -61,11 +61,10 @@ export const initPage = async ({
req: {
host: headers.get('host'),
i18n,
query: qs.parse(queryString, {
depth: 10,
ignoreQueryPrefix: true,
query: parse(queryString, {
nestingSyntax: 'index',
}),
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
url: `${payload.config.serverURL}${route}${searchParams ? `?${queryString}` : ''}`,
} as PayloadRequestWithData,
},
payload,
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/utilities/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Icon } from 'next/dist/lib/metadata/types/metadata-types.js'
import type { MetaConfig } from 'payload'

import { payloadFaviconDark, payloadFaviconLight, staticOGImage } from '@payloadcms/ui/assets'
import qs from 'qs'
import { stringify } from 'picoquery'

const defaultOpenGraph = {
description:
Expand Down Expand Up @@ -58,13 +58,13 @@ export const meta = async (args: MetaConfig & { serverURL: string }): Promise<an
{
alt: ogTitle,
height: 630,
url: `/api/og${qs.stringify(
url: `/api/og?${stringify(
{
description: openGraphFromProps?.description || defaultOpenGraph.description,
title: ogTitle,
},
{
addQueryPrefix: true,
nestingSyntax: 'index',
},
)}`,
width: 1200,
Expand Down
6 changes: 4 additions & 2 deletions packages/next/src/views/Version/SelectComparison/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
useTranslation,
} from '@payloadcms/ui'
import { formatDate } from '@payloadcms/ui/shared'
import qs from 'qs'
import { stringify } from 'picoquery'
import React, { useCallback, useEffect, useState } from 'react'

import type { Props } from './types.js'
Expand Down Expand Up @@ -74,7 +74,9 @@ export const SelectComparison: React.FC<Props> = (props) => {
})
}

const search = qs.stringify(query)
const search = stringify(query, {
nestingSyntax: 'index',
})

const response = await fetch(`${baseURL}?${search}`, {
credentials: 'include',
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"is-buffer": "^2.0.5",
"md5": "2.3.0",
"object-to-formdata": "4.5.1",
"qs": "6.12.1",
"picoquery": "2.3.1",
"react-animate-height": "2.1.2",
"react-datepicker": "6.9.0",
"react-image-crop": "10.1.8",
Expand All @@ -104,7 +104,6 @@
"@hyrious/esbuild-plugin-commonjs": "^0.2.4",
"@payloadcms/eslint-config": "workspace:*",
"@types/body-scroll-lock": "^3.1.0",
"@types/qs": "6.9.7",
"@types/react": "npm:[email protected]",
"@types/react-datepicker": "6.2.0",
"@types/react-dom": "npm:[email protected]",
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useModal } from '@faceless-ui/modal'
import qs from 'qs'
import { stringify } from 'picoquery'
import React, { useCallback, useEffect, useState } from 'react'
import { toast } from 'sonner'

Expand Down Expand Up @@ -41,7 +41,9 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
const [isOpen, setIsOpen] = useState(false)
const [collectionConfig] = useRelatedCollections(collectionSlug)
const { formQueryParams } = useFormQueryParams()
const formattedQueryParams = qs.stringify(formQueryParams)
const formattedQueryParams = stringify(formQueryParams, {
nestingSyntax: 'index',
})

const { componentMap } = useComponentMap()

Expand Down
8 changes: 4 additions & 4 deletions packages/ui/src/elements/SortComplex/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getTranslation } from '@payloadcms/translations'
import { usePathname, useRouter } from 'next/navigation.js'
import { sortableFieldTypes } from 'payload'
import { fieldAffectsData } from 'payload/shared'
import qs from 'qs'
import { stringify } from 'picoquery'
import React, { useEffect, useState } from 'react'

export type SortComplexProps = {
Expand Down Expand Up @@ -57,13 +57,13 @@ export const SortComplex: React.FC<SortComplexProps> = (props) => {
if (handleChange) handleChange(newSortValue)

if (searchParams.sort !== newSortValue && modifySearchQuery) {
const search = qs.stringify(
const search = `?${stringify(
{
...searchParams,
sort: newSortValue,
},
{ addQueryPrefix: true },
)
{ nestingSyntax: 'index' },
)}`

router.replace(`${pathname}${search}`)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'
import type { TypeWithID } from 'payload'

import qs from 'qs'
import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react'

import { useDebounce } from '../../../hooks/useDebounce.js'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'
import type { PaginatedDocs, Where } from 'payload'

import qs from 'qs'
import { stringify } from 'picoquery'
import React, { useCallback, useEffect, useReducer, useState } from 'react'

import type { Option } from '../../../ReactSelect/types.js'
Expand Down Expand Up @@ -90,7 +90,7 @@ export const RelationshipField: React.FC<Props> = (props) => {

try {
const response = await fetch(
`${serverURL}${api}/${relationSlug}${qs.stringify(query, { addQueryPrefix: true })}`,
`${serverURL}${api}/${relationSlug}?${stringify(query, { nestingSyntax: 'index' })}`,
{
credentials: 'include',
headers: {
Expand Down
10 changes: 7 additions & 3 deletions packages/ui/src/fields/Relationship/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { PaginatedDocs, Where } from 'payload'

import { wordBoundariesRegex } from 'payload/shared'
import qs from 'qs'
import { stringify } from 'picoquery'
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'

import type { DocumentDrawerProps } from '../../elements/DocumentDrawer/types.js'
Expand Down Expand Up @@ -201,7 +201,9 @@ const _RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
}

const response = await fetch(`${serverURL}${api}/${relation}`, {
body: qs.stringify(query),
body: stringify(query, {
nestingSyntax: 'index',
}),
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
Expand Down Expand Up @@ -330,7 +332,9 @@ const _RelationshipField: React.FC<RelationshipFieldProps> = (props) => {

if (!errorLoading) {
const response = await fetch(`${serverURL}${api}/${relation}`, {
body: qs.stringify(query),
body: stringify(query, {
nestingSyntax: 'index',
}),
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/forms/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { dequal } from 'dequal/lite' // lite: no need for Map and Set support
import { useRouter } from 'next/navigation.js'
import { serialize } from 'object-to-formdata'
import { wait } from 'payload/shared'
import qs from 'qs'
import { stringify } from 'picoquery'
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import { toast } from 'sonner'

Expand Down Expand Up @@ -279,7 +279,7 @@ export const Form: React.FC<FormProps> = (props) => {
const actionEndpoint =
actionArg ||
(typeof action === 'string'
? `${action}${qs.stringify(formQueryParams, { addQueryPrefix: true })}`
? `${action}?${stringify(formQueryParams, { nestingSyntax: 'index' })}`
: null)

if (actionEndpoint) {
Expand Down Expand Up @@ -629,7 +629,7 @@ export const Form: React.FC<FormProps> = (props) => {

const actionString =
typeof action === 'string'
? `${action}${qs.stringify(formQueryParams, { addQueryPrefix: true })}`
? `${action}${stringify(formQueryParams, { nestingSyntax: 'index' })}`
: ''

return (
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/src/hooks/usePayloadAPI.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import qs from 'qs'
import { stringify } from 'picoquery'
import { useEffect, useRef, useState } from 'react'

import { useLocale } from '../providers/Locale/index.js'
Expand Down Expand Up @@ -35,15 +35,15 @@ export const usePayloadAPI: UsePayloadAPI = (url, options = {}) => {
const { code: locale } = useLocale()
const hasInitialized = useRef(false)

const search = qs.stringify(
const search = `?${stringify(
{
locale,
...(typeof params === 'object' ? params : {}),
},
{
addQueryPrefix: true,
nestingSyntax: 'index',
},
)
)}`

// If `initialData`, no need to make a request
useEffect(() => {
Expand Down
Loading