SWR Vercel Global Config using Typescript

Viewed 2227

I want to create an object with SWR Configs to set them global. I'm using ConfigInterface type that comes from swr.

import { ConfigInterface } from 'swr';

import fetcher from 'api/axiosFetcher';

const swrConfig: ConfigInterface = {
  fetcher,
};

export default swrConfig;

The problem is when I try to use this object inside SWRConfig

<SWRConfig value={swrConfig}>

displays the next error

Type 'ConfigInterface<any, any, fetcherFn<any>>' is not assignable to type 'ConfigInterface<any, any, (...args: any) => any>'.
  Types of property 'fetcher' are incompatible.
    Type '((...args: any) => any) | null | undefined' is not assignable to type '((...args: any) => any) | undefined'.
      Type 'null' is not assignable to type '((...args: any) => any) | undefined'.ts(2322)

My fetcher function

const fetcher = <T>(url: string): Promise<T> =>
  axiosInstance
    .get(url)
    .then((response) => {
      if (response.statusText === 'OK') {
        return response.data;
      }
      throw new Error(response.statusText);
    })
    .catch(({ response }) => {
      throw new Error(response.statusText);
    });
1 Answers

Wrote up some custom TypeDefs for your question.

  • First, write your function as a TypeScript Class, one that accepts a generic argument (T). In this way, you can apply a constraint (T) on a class which the Mixin is applied to
type FetcherConstructor<T = {}> = new (...args: any[]) => T;

  • Our class only works if the url parameter is passed a string value -- the required url parameter is an example of implementing an applied constraint (mentioned above)
type FetcherAbstracted = FetcherConstructor<{
    fetcher: (url: string, init?: RequestInit) => Promise<Response>;
}>;

As you can see, the conditional RequestInit interface is quite extensive

interface RequestInit {
    /**
     * A BodyInit object or null to set request's body.
     */
    body?: BodyInit | null;
    /**
     * A string indicating how the request will interact with the browser's cache to set request's cache.
     */
    cache?: RequestCache;
    /**
     * A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials.
     */
    credentials?: RequestCredentials;
    /**
     * A Headers object, an object literal, or an array of two-item arrays to set request's headers.
     */
    headers?: HeadersInit;
    /**
     * A cryptographic hash of the resource to be fetched by request. Sets request's integrity.
     */
    integrity?: string;
    /**
     * A boolean to set request's keepalive.
     */
    keepalive?: boolean;
    /**
     * A string to set request's method.
     */
    method?: string;
    /**
     * A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode.
     */
    mode?: RequestMode;
    /**
     * A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect.
     */
    redirect?: RequestRedirect;
    /**
     * A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer.
     */
    referrer?: string;
    /**
     * A referrer policy to set request's referrerPolicy.
     */
    referrerPolicy?: ReferrerPolicy;
    /**
     * An AbortSignal to set request's signal.
     */
    signal?: AbortSignal | null;
    /**
     * Can only be null. Used to disassociate request from any Window.
     */
    window?: any;
}
  • Where the bodyInit type is defined as follows
type BodyInit = Blob | BufferSource | FormData | URLSearchParams | ReadableStream<Uint8Array> | string;
  • And the Response interface, which is typeof fetcher in this example, has the following response interface as its returned payload
/** This Fetch API interface represents the response to a request. */
interface Response extends Body {
    readonly headers: Headers;
    readonly ok: boolean;
    readonly redirected: boolean;
    readonly status: number;
    readonly statusText: string;
    readonly trailer: Promise<Headers>;
    readonly type: ResponseType;
    readonly url: string;
    clone(): Response;
}

Check out mixins here for more clarification.

  • A straightforward practical implementation
export default async function FetcherExample(
    fetch: FetcherAbstracted
) {
    const exactPath = 'docs/handbook/mixins.html';

    const data: Response = await new fetch().fetcher(
        `https://typescriptlang.org${exactPath}`,
        {
            body: exactPath,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
                // ...
            },
            credentials: "include"
        }
    );

    return await data.json();
}

Bringing it all together now in one code snippet for clarity

export type FetcherConstructor<T = {}> = new (...args: any[]) => T;

export type FetcherAbstracted = FetcherConstructor<{
    fetcher: (url: string, init?: RequestInit) => Promise<Response>;
}>;

export default async function FetcherExample(
    fetch: FetcherAbstracted
) {
    const exactPath = 'docs/handbook/mixins.html';

    const data: Response = await new fetch().fetcher(
        `https://typescriptlang.org${exactPath}`,
        {
            body: exactPath,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
                // ...
            },
            credentials: "include"
        }
    );

    return await data.json();
}

UPDATE

To answer your question concisely, this hook should work with axios+SWR, which is similar to the parameter-constrained generic I wrote out above

type FetcherAbstracted = FetcherConstructor<{
    fetcher: (url: string, init?: RequestInit) => Promise<Response>;
}>;

So much so you could use it for your Axios fetcher implementation.

const fetcher: FetcherAbstracted = async (url: string) => {
  try {
    const res = await Axios.get(url);
    return res.data;
  } catch (error) {
    throw error.response.data;
  }
};
Related