Skip to content
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

Using mutate with optimisticData: typescript is giving me a hard time #2406

Closed
svenlombaert opened this issue Feb 2, 2023 · 1 comment · Fixed by #2421
Closed

Using mutate with optimisticData: typescript is giving me a hard time #2406

svenlombaert opened this issue Feb 2, 2023 · 1 comment · Fixed by #2421

Comments

@svenlombaert
Copy link

svenlombaert commented Feb 2, 2023

Edit: Posted this as an issue as well as it seems that the types in the library are wrong

Hi there,

Was just upgrading from 1.x to 2.x. I love the Optimistic UI approach, it's removing a lot of our boilerplate. I have one issue though, related to typescript.

I'm using swr like this:

const useRequest = <Data = any, Error = unknown>(
  request: GetRequest | null,
  {
    fallbackData,
    revalidateOnEvent = null,
    shouldFetch = true,
    ...config
  }: Config<Data, Error> = {},
): Return<Data, Error> => {
  const {
    data: response,
    error,
    isValidating,
    mutate,
  } = useSWR<AxiosResponse<Data>, AxiosError<Error>>(
    shouldFetch ? request && JSON.stringify(request) : null,
    () => {
      const axiosConfig = api.getAxiosConfig() as unknown as AxiosRequestConfig;

      return api.axios({
        ...axiosConfig,
        ...request,
        headers: {
          ...axiosConfig.headers,
          ...request?.headers,
        },
      });
    },
    {
      revalidateOnMount: true,
      ...config,
      fallbackData: fallbackData && {
        status: 200,
        statusText: 'InitialData',
        config: request as AxiosRequestConfig,
        headers: {},
        data: fallbackData,
      },
    },
  );

Basically telling that we receive an AxiosResponse with a generic Data type inside it's data key. That's all good. Because of this, mutate is automatically inferred to this type:

const mutate: (data?: AxiosResponse<NotificationSettingApiResponse, any> | Promise<AxiosResponse<NotificationSettingApiResponse, any>> | MutatorCallback<...> | undefined, opts?: boolean | ... 1 more ... | undefined) => Promise<...>

And I mutate like this:

  const updateNotificationStatus = useCallback(
    async (isEnabled: boolean) => {
      await api.post('/direct-post/notification', {
        coinId: asset.id,
        isEnabled: isEnabled,
      });
    },
    [asset.id],
  );

  const handleEnableNotifications = useCallback(
    async (enabled: boolean) => {
      await mutate(updateNotificationStatus(enabled), {
        optimisticData: (response) => {
          if (!response) {
            return;
          }

          return {
            ...response,
            data: {
              ...response.data,
              notificationSetting: {
                ...response.data?.notificationSetting,
                isEnabled: enabled,
              },
            },
          };
        },
        populateCache: false,
        revalidate: true,
      });
    },
    [mutate, updateNotificationStatus],
  );

Code works perfectly, gets rolled back on error, all good. It's just that typescript is complaining that updateNotificationStatus is returning a Promise<void> whilst it expects Promise<AxiosResponse<NotificationSettingApiResponse, any>>

Anyone has found a way to tell typescript this is fine? Because if you set populateCache: false, it doesn't matter what updateNotificationStatus returns, it's not used anyway.

Hope this was clear 😅

Originally posted by @svenlombaert in #2358

promer94 added a commit to promer94/swr that referenced this issue Feb 11, 2023
shuding pushed a commit that referenced this issue Feb 11, 2023
* types: fix some mutation type issue

close #2418 #2406 #2280

* chore: update

* fix lint
@svenlombaert
Copy link
Author

svenlombaert commented Feb 15, 2023

@shuding I checked out https://pkg.csb.dev/vercel/swr/commit/b4df51d9/swr and indeed now, the updaterFn doesn't trigger TS errors anymore when I explicitly return undefined from it.

I do have a second issue, is there a reason why optimisticData can't be undefined? I have a pattern like this:

const updateNotificationStatus = useCallback(
    async (isEnabled: boolean) => {
      await api.post('/direct-post/notification', {
        coinId: asset.id,
        isEnabled: isEnabled,
      });

      return undefined;
    },
    [asset.id],
  );

  const handleEnableNotifications = useCallback(
    async (enabled: boolean) => {
      try {
        await mutate(updateNotificationStatus(enabled), {
          optimisticData: (response) => {
            if (!response) {
              return undefined;
            }

            return {
              ...response,
              data: {
                ...response.data,
                notificationSetting: {
                  ...response.data?.notificationSetting,
                  isEnabled: enabled,
                },
              },
            };
          },
          populateCache: false,
          revalidate: true,
        });
      } catch (e) {
        ErrorHandler.catch(e);
      }
    },
    [mutate, updateNotificationStatus],
  );

response inside the optimisticData function can be undefined, which means I can't mutate the response, thus I just want to return undefined again. Currently typescript throws an error on that because it expects an actual AxiosResponse

(parameter) response: AxiosResponse<NotificationSettingApiResponse, any> | undefined

Type '(response: AxiosResponse<NotificationSettingApiResponse, any> | undefined) => { data: { notificationSetting: { isEnabled: boolean; }; }; ... 4 more ...; request?: any; } | undefined' is not assignable to type 'AxiosResponse<NotificationSettingApiResponse, any> | ((currentData?: AxiosResponse<NotificationSettingApiResponse, any> | undefined) => AxiosResponse<...>) | undefined'.
  Type '(response: AxiosResponse<NotificationSettingApiResponse, any> | undefined) => { data: { notificationSetting: { isEnabled: boolean; }; }; ... 4 more ...; request?: any; } | undefined' is not assignable to type '(currentData?: AxiosResponse<NotificationSettingApiResponse, any> | undefined) => AxiosResponse<NotificationSettingApiResponse, any>'.
    Type '{ data: { notificationSetting: { isEnabled: boolean; }; }; status: number; statusText: string; headers: AxiosResponseHeaders; config: AxiosRequestConfig<any>; request?: any; } | undefined' is not assignable to type 'AxiosResponse<NotificationSettingApiResponse, any>'.
      Type 'undefined' is not assignable to type 'AxiosResponse<NotificationSettingApiResponse, any>'.ts(2322)
index.tsx(55, 27): Did you mean to call this expression?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants