Wednesday, 8 March 2023

SWR mutation does not update the cache using a static method of class as a fetcher

I'm using the swr package. I successfully fetch data using useSWR, but when I try to mutate the data - it does not work, and the cached state of swr does not change (as it should).

I created my custom hook:

import useSWR from 'swr';

import BackendService from '@/services/backend';

const useBackend = <D, E = unknown>(path: string | null) => {
    const { data, error, isLoading, mutate } = useSWR<D, E>(path, BackendService.get);

    return { data, error, isLoading, mutate };
};

export default useBackend;

And this is my BackendService:

import { preload } from 'swr';

import type { IHttpMethod } from '@/interfaces/http';

class BackendService {
    private static routesWithRefreshToken: string[] = ['/user/auth'];

    private static fetcher = async <R = unknown, D = unknown>(
        path: string,
        method: IHttpMethod,
        data?: D,
    ) => {
        const requestPath = import.meta.env.VITE_BACKEND_URL + path;
        const withRefresh = this.routesWithRefreshToken.includes(path);
        const token = withRefresh ? localStorage.getItem('token') : sessionStorage.getItem('token');

        const res = await fetch(requestPath, {
            method,
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type': 'application/json',
            },
            body: data ? JSON.stringify(data) : undefined,
        });

        if (!res.ok) {
            throw new Error();
        }

        const resData = await res.json().catch(() => undefined);

        return resData as R;
    };

    public static get = <R = unknown>(path: string) => {
        return this.fetcher<R, null>(path, 'GET');
    };

    public static post = <R = unknown, D = unknown>(path: string, data?: D) => {
        return this.fetcher<R, D>(path, 'POST', data);
    };

    public static patch = <R = unknown, D = unknown>(path: string, data?: D) => {
        return this.fetcher<R, D>(path, 'PATCH', data);
    };

    public static delete = <R = unknown>(path: string) => {
        return this.fetcher<R, null>(path, 'DELETE');
    };

    public static preload = (path: string) => {
        return preload(path, this.get);
    };
}

export default BackendService;

Now, I have the following code:

import React, { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import type { IGetAllSecretsResponseData } from '@exlint.io/common';

import useBackend from '@/hooks/use-backend';
import BackendService from '@/services/backend';

import SecretManagementView from './SecretManagement.view';

interface IProps {}

const SecretManagement: React.FC<IProps> = () => {
    const navigate = useNavigate();

    const { data: getAllSecretsResponseData, mutate: getAllSecretsMutate } =
        useBackend<IGetAllSecretsResponseData>('/user/secrets');

    const hasSecrets = useMemo(() => {
        if (!getAllSecretsResponseData) {
            return false;
        }

        return getAllSecretsResponseData.secrets.length > 0;
    }, [getAllSecretsResponseData]);

    const onRevokeAllSecrets = async () => {
        await getAllSecretsMutate(
            async () => {
                await BackendService.delete('/user/secrets');

                return {
                    secrets: [],
                };
            },
            {
                optimisticData: { secrets: [] },
                rollbackOnError: true,
            },
        );

        navigate('', { replace: true });
    };

    return <SecretManagementView hasSecrets={hasSecrets} onRevokeAllSecrets={onRevokeAllSecrets} />;
};

SecretManagement.displayName = 'SecretManagement';
SecretManagement.defaultProps = {};

export default React.memo(SecretManagement);

So when the onRevokeAllSecrets is executed - the cached state does not change.

But if I use mutate of useSWRConfig it works:

import React, { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import type { IGetAllSecretsResponseData } from '@exlint.io/common';
import { useSWRConfig } from 'swr';

import useBackend from '@/hooks/use-backend';
import BackendService from '@/services/backend';

import SecretManagementView from './SecretManagement.view';

interface IProps {}

const SecretManagement: React.FC<IProps> = () => {
    const navigate = useNavigate();

    const { mutate } = useSWRConfig();

    const { data: getAllSecretsResponseData, mutate: getAllSecretsMutate } =
        useBackend<IGetAllSecretsResponseData>('/user/secrets');

    const hasSecrets = useMemo(() => {
        if (!getAllSecretsResponseData) {
            return false;
        }

        return getAllSecretsResponseData.secrets.length > 0;
    }, [getAllSecretsResponseData]);

    const onRevokeAllSecrets = async () => {
        await getAllSecretsMutate(
            async () => {
                await BackendService.delete('/user/secrets');

                return {
                    secrets: [],
                };
            },
            {
                optimisticData: { secrets: [] },
                rollbackOnError: true,
            },
        );

        mutate('/user/secrets', { secrets: [] });

        navigate('', { replace: true });
    };

    return <SecretManagementView hasSecrets={hasSecrets} onRevokeAllSecrets={onRevokeAllSecrets} />;
};

SecretManagement.displayName = 'SecretManagement';
SecretManagement.defaultProps = {};

export default React.memo(SecretManagement);

Could anyone tell why? I checked and my BackendService.delete call completes successfully.



from SWR mutation does not update the cache using a static method of class as a fetcher

No comments:

Post a Comment