swr
swr copied to clipboard
Can't use 'null' as 'url' for axios
Bug report
Description / Observed Behavior
When trying to use the doco's suggestion of passing null
as the URL (https://swr.vercel.app/docs/conditional-fetching), Axios throws an exception:
stack: 'TypeError: Cannot read properties of null (reading 'indexOf')
at buildURL (webpack-internal:///./node_modules/axios/lib/helpers/buildURL.js:61:29)
at dispatchXhrRequest (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:46:47)
at new Promise (<anonymous>)
at xhrAdapter (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:16:10)
at dispatchRequest (webpack-internal:///./node_modules/axios/lib/core/dispatchRequest.js:58:10)
at Axios.request (webpack-int…ebpack-internal:///./node_modules/swr/dist/index.mjs:40:53)
at eval (webpack-internal:///./node_modules/swr/dist/index.mjs:33:71)
at new Promise (<anonymous>)
at __awaiter (webpack-internal:///./node_modules/swr/dist/index.mjs:29:12)
at eval (webpack-internal:///./node_modules/swr/dist/index.mjs:709:108)
at Array.onRevalidate (webpack-internal:///./node_modules/swr/dist/index.mjs:937:21)
at revalidateAllKeys (webpack-internal:///./node_modules/swr/dist/index.mjs:395:33)'
If you provide the third type to useSWR to see the typings, you just get red lines because url
cannot be null
to axios.get:
type AxiosGet = Parameters<typeof axios.get>
const url = (
requestPath: string | null | undefined = null,
params: Record<string, any> = {},
version: string = "v1",
) => {
if (requestPath === null) return null
[...]
}
const {data, error} = useSWR<any, any, AxiosGet>(
[url(requestPath, params), {
params: params,
}],
axios.get
)
No overload matches this call.
Overload 1 of 8, '(key: [url: string, config?: AxiosRequestConfig<unknown> | undefined], fetcher: ((url: string, config?: AxiosRequestConfig<unknown> | undefined) => any) | null): SWRResponse<...>', gave the following error.
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.
Overload 2 of 8, '(key: [url: string, config?: AxiosRequestConfig<unknown> | undefined], config: Partial<PublicConfiguration<any, any, (url: string, config?: AxiosRequestConfig<unknown> | undefined) => any>> | undefined): SWRResponse<...>', gave the following error.
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'
Therefore any dynamic error handler using useEffect will throw this to the UI.
Expected Behavior
useSWR should have a way to separate the key from the url, however using "" with axios seems to request the root URL of the site so it returns HTML data if you return an empty string instead of null
Repro Steps / Code Example
See above
Additional Context
SWR version. 1.3.0 Add any other context about the problem here.
As a workaround, I send the "" string as URL to axios anyway and allow the request to continue...
Then I just check if the path was actually "" and return null data...
const url = (
requestPath: string | null | undefined = null,
params: Record<string, any> = {},
version: string = "v1",
) => {
if (requestPath === null) return ""
}
const { data, error } = useSWR<any, any, AxiosGet>(
[url(requestPath, params), {
params: params,
}],
axios.get
)
// Workaround for not being able to use null url with axios and useSWR
if (requestPath === null) return resolve({
data: null,
lastModified: new Date()
})
Seems you're always passing an array key to useSWR which will get stringified, have you tried to use requestPath ? url(...) : null
as key?
I tried multiple variations based on what you said below. When using Parameters[url: string, options: {}]
so the key becomes this.
Using an array is supposed to be supported: https://swr.vercel.app/docs/arguments
const { data, error } = useSWR<any, any, AxiosGet>(
requestPath ? [url(requestPath, params), {
params: params,
}] : null,
axios.get
)
const { data, error } = useSWR<any, any, AxiosGet>(
[requestPath ? url(requestPath, params), {
params: params,
} : null],
axios.get
)
const { data, error } = useSWR<any, any, AxiosGet>(
[requestPath ? url(requestPath, params) : null, {
params: params,
}],
axios.get
)
This is defined here as supporting a tuple:
https://github.com/vercel/swr/blob/85c84ceb578cb3d28362659bd9b82a03fd39be6b/_internal/types.ts#L249
SWRKey extends Key
which is Arguments
...
export declare type Arguments = string | ArgumentsTuple | Record<any, any> | null | undefined | false;
However, axios does not support null
urls, so it reports an error...
I wonder if it would be possible to have a separate key
apart from the url
and other arguments passed to axios, so they key could be set null while the url remains a real url? 🤷
Ok this works... Since axios.request
url
accepts undefined
... Maybe something for the doco if people want the typings to not use Parameters<typeof axios.get>
and instead use Parameters<typeof axios.request>
?
type AxiosRequest = Parameters<typeof axios.request>
const { data, error } = useSWR<any, any, AxiosRequest>(
[{
url: url(requestPath, params),
params: params,
}],
axios.request
)
const url = (
requestPath: string | null | undefined,
params: Record<string, any> = {},
version: string = "v1",
) => {
if (!requestPath) return
}
This can be closed then I guess
It seems I spoke too soon, axios.request also throws an error when using undefined
as the URL. It would be nice to have control over when axios is called to avoid it... I really want to use the typings so that it is easier to write the options for the axios request. But this just makes its way up to my error handler:
TypeError: Cannot read properties of undefined (reading 'protocol')
at isURLSameOrigin (isURLSameOrigin.js?8795:57:1)
at dispatchXhrRequest (xhr.js?1a5c:147:1)
at new Promise (<anonymous>)
at xhrAdapter (xhr.js?1a5c:16:1)
at dispatchRequest (dispatchRequest.js?4dc9:58:1)
at Axios.request (Axios.js?29fb:109:1)
at wrap (bind.js?4bea:9:1)
at eval (index.mjs?d1eb:757:1)
at step (index.mjs?d1eb:50:1)
at Object.eval [as next] (index.mjs?d1eb:31:46)
at eval (index.mjs?d1eb:24:1)
at new Promise (<anonymous>)
at __awaiter (index.mjs?d1eb:20:1)
at eval (index.mjs?d1eb:700:62)
at eval (index.mjs?d1eb:960:1)
at commitHookEffectListMount (react-dom.development.js?ac89:23150:1)
at commitLayoutEffectOnFiber (react-dom.development.js?ac89:23268:1)
at commitLayoutMountEffects_complete (react-dom.development.js?ac89:24688:1)
at commitLayoutEffects_begin (react-dom.development.js?ac89:24674:1)
at commitLayoutEffects (react-dom.development.js?ac89:24612:1)
at commitRootImpl (react-dom.development.js?ac89:26823:1)
at commitRoot (react-dom.development.js?ac89:26682:1)
at finishConcurrentRender (react-dom.development.js?ac89:25981:1)
at performConcurrentWorkOnRoot (react-dom.development.js?ac89:25809:1)
at workLoop (scheduler.development.js?bcd2:266:1)
at flushWork (scheduler.development.js?bcd2:239:1)
at MessagePort.performWorkUntilDeadline (scheduler.development.js?bcd2:533:1)
To workaround it, I check for an error returned by useSWR and if the url was undefined... if I don't, it makes its way to my error handler:
[...]
if (error) {
if (
url !== undefined
&& error.request
&& error.request.status !== 404
) return reject(error)