qs-esm is a qs fork I created and doesn't add bloated polyfills, is ESM-only, has a smaller bundle size and comes with types included. qs: https://bundlephobia.com/package/qs@6.12.1 (11kb) https://npm.anvaka.com/#/view/2d/qs (15 dependencies) qs-esm: https://bundlephobia.com/package/qs-esm@7.0.0 (4.2kb) https://npm.anvaka.com/#/view/2d/qs-esm (1 dependency) I don't agree with the backwards philosophy of qs: https://github.com/ljharb/qs/issues/404#issuecomment-806392831 ("more deps is better", lower bundle size as opt-in, maximum environment compatibility as opt-out) qs imports waaay too many useless dependencies
105 lines
2.4 KiB
TypeScript
105 lines
2.4 KiB
TypeScript
'use client'
|
|
import * as qs from 'qs-esm'
|
|
import { useEffect, useRef, useState } from 'react'
|
|
|
|
import { useLocale } from '../providers/Locale/index.js'
|
|
import { useTranslation } from '../providers/Translation/index.js'
|
|
import { requests } from '../utilities/api.js'
|
|
|
|
type Result = [
|
|
{
|
|
data: any
|
|
isError: boolean
|
|
isLoading: boolean
|
|
},
|
|
{
|
|
setParams: React.Dispatch<unknown>
|
|
},
|
|
]
|
|
|
|
type Options = {
|
|
initialData?: any
|
|
initialParams?: unknown
|
|
}
|
|
|
|
type UsePayloadAPI = (url: string, options?: Options) => Result
|
|
|
|
export const usePayloadAPI: UsePayloadAPI = (url, options = {}) => {
|
|
const { initialData, initialParams = {} } = options
|
|
|
|
const { i18n } = useTranslation()
|
|
const [data, setData] = useState(initialData || {})
|
|
const [params, setParams] = useState(initialParams)
|
|
const [isLoading, setIsLoading] = useState(!initialData)
|
|
const [isError, setIsError] = useState(false)
|
|
const { code: locale } = useLocale()
|
|
const hasInitialized = useRef(false)
|
|
|
|
const search = qs.stringify(
|
|
{
|
|
locale,
|
|
...(typeof params === 'object' ? params : {}),
|
|
},
|
|
{
|
|
addQueryPrefix: true,
|
|
},
|
|
)
|
|
|
|
// If `initialData`, no need to make a request
|
|
useEffect(() => {
|
|
if (initialData && !hasInitialized.current) {
|
|
hasInitialized.current = true
|
|
return
|
|
}
|
|
|
|
const abortController = new AbortController()
|
|
|
|
const fetchData = async () => {
|
|
setIsError(false)
|
|
setIsLoading(true)
|
|
|
|
try {
|
|
const response = await requests.get(`${url}${search}`, {
|
|
headers: {
|
|
'Accept-Language': i18n.language,
|
|
},
|
|
signal: abortController.signal,
|
|
})
|
|
|
|
if (response.status > 201) {
|
|
setIsError(true)
|
|
}
|
|
|
|
const json = await response.json()
|
|
setData(json)
|
|
setIsLoading(false)
|
|
} catch (error) {
|
|
if (!abortController.signal.aborted) {
|
|
setIsError(true)
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (url) {
|
|
void fetchData()
|
|
} else {
|
|
setIsError(false)
|
|
setIsLoading(false)
|
|
}
|
|
|
|
return () => {
|
|
abortController.abort()
|
|
}
|
|
}, [url, locale, search, i18n.language, initialData])
|
|
|
|
// If `initialData` changes, reset the state
|
|
useEffect(() => {
|
|
if (initialData && hasInitialized.current) {
|
|
setData(initialData)
|
|
}
|
|
}, [initialData])
|
|
|
|
return [{ data, isError, isLoading }, { setParams }]
|
|
}
|