RTK Query Optimistic update does not work

92 views Asked by At

I'm fetching the list of users with the possibility to create requests to friends and when I press the "Add Friend" button for one user I want to change the button text to "Request sent" using RTK Query optimistic updates but getting nothing update

Here I'm fetching list of users

'use client';

import UserCard from '@/components/UserCard';
import Pagination from '@/components/Pagination';
import { useTranslations } from 'next-intl';
import { useGetAllUsersQuery } from '@/services/user';
import { useSearchParams } from 'next/navigation';
import { useRouter } from 'next-intl/client';
import { useAppDispatch, useAppSelector } from '@/hooks';
import { selectContacts, selectCount } from '@/redux/slices/userSlice';
import { useEffect, useState } from 'react';

export default function FriendsList() {
  const t = useTranslations();
  const search = useSearchParams();
  const router = useRouter();
  const query = search.get('q') || '';
  const page = search.get('page') || '1';
  const limit = search.get('limit') || '12';
  const count = useAppSelector(selectCount);
  // const friendsInfo = useAppSelector(selectContacts);
  const { data, isFetching } = useGetAllUsersQuery(
    { page, limit, query }
    // { refetchOnMountOrArgChange: true }
  );
  const friendsInfo = data?.users;
  console.log(data);
  const handlePageChange = async (formData: FormData) => {
    const newPage = formData.get('page');

    router.push(`/contacts?page=${newPage}${query && `&q=${query}`}`);
  };

  if (isFetching) {
    return (
      <div className='flex grow w-[100%]'>
        <div className='grid grid-cols-[1fr] gap-[16px] w-[100%] mobile:grid-cols-[1fr_1fr_1fr] tablet:grid-cols-[1fr_1fr_1fr_1fr]'>
          {new Array(12).fill(0).map((_, index) => (
            <div
              key={index}
              className='animate-pulse max-h-[217px] h-[217px] bg-gray-300 rounded-[0.25rem]'
            ></div>
          ))}
        </div>
      </div>
    );
  }

  if (friendsInfo?.length === 0) {
    return (
      <>
        <div className='flex justify-center items-center w-[100%] grow'>
          <p className='text-[14px] text-[#adb5bd]'>
            {t('notFoundAnyFriends')} {query && t('withNameOrUsername', { name: query })}
          </p>
        </div>
        <form className='flex justify-center items-center gap-[20px]' action={handlePageChange}>
          <Pagination page={+page} maxPages={Math.ceil(count / limit)} />
        </form>
      </>
    );
  }

  return (
    <>
      <div className='flex grow w-[100%] mb-[60px]'>
        <div className='grid grid-cols-[1fr] gap-[16px] w-[100%] mobile:grid-cols-[1fr_1fr_1fr] tablet:grid-cols-[1fr_1fr_1fr_1fr]'>
          {friendsInfo?.map((user) => <UserCard key={user._id} {...user} />)}
        </div>
      </div>
      <form className='flex justify-center items-center gap-[20px]' action={handlePageChange}>
        <Pagination page={+page} maxPages={Math.ceil(count / limit)} />
      </form>
    </>
  );
}

Here is my "Add button" button

'use client';

import NoAvatar from '@/components/NoAvatar';
import FriendsButton from '@/components/buttons/FriendsButton';
import Image from 'next/image';
import { GetFriendUser } from '@/utils/types/UserApi';
import { FriendStatus } from '@/utils/types/AppTypes';
import { useTranslations } from 'next-intl';
import {
  friendsApi,
  useAddFriendMutation,
  useAnswerFriendsRequestMutation
} from '@/services/friend';
import SpinnerPulse from '@/components/SpinnerPulse';
import { useAppDispatch } from '@/hooks';
import { userApi } from '@/services/user';
import { appApi } from '@/services/api';

export default function UserCard({
  _id,
  friend_status,
  image,
  fullName,
  is_active,
  username,
  request_id,
  avatar_color
}: GetFriendUser) {
  const t = useTranslations();
  const [addFriend, { isLoading }] = useAddFriendMutation();
  const [answerFriendRequest, { isLoading: isLoadingAnswer }] = useAnswerFriendsRequestMutation();
  const handleAddFriend = async () => {
    await addFriend({ id: _id });
  };
  const handleAnswerRequest = async (status: 'accept' | 'reject') => {
    answerFriendRequest({ id: request_id, status });
  };
  const getButtonColor = () => {
    switch (friend_status) {
      case FriendStatus.PENDING:
        return { text: t('pending'), color: 'bg-yellow-300' };
      case FriendStatus.ACCEPTED:
        return { text: t('friend'), color: 'bg-sky-400' };
      default:
        return { text: t('addFriend'), color: 'bg-[#10d876]' };
    }
  };
  const buttonInfo = getButtonColor();

  return (
    <div className='flex flex-col items-center p-[16px_16px_24px_16px] gap-[16px] bg-[#FFFFFF] max-h-[217px] rounded-[0.25rem] shadow-[0_8px_30px_rgba(0,0,0,0.08)]'>
      {!!image ? (
        <Image
          src={image}
          alt='User'
          width={65}
          height={65}
          className='rounded-full'
          style={{ aspectRatio: 1 }}
        />
      ) : (
        <NoAvatar
          size='w-[65px] h-[65px]'
          name={fullName}
          isOnline={is_active!}
          avatarColor={avatar_color}
          textSize='text-[20px]'
        />
      )}
      <div className={`flex flex-col items-center gap-[4px] ${!username && 'mb-[19px]'}`}>
        <p className='font-[700] text-[14px]'>{fullName}</p>
        {!!username && <span className='text-[10px] text-[#adb5bd] font-[500]'>@${username}</span>}
      </div>
      {friend_status === FriendStatus.WAITING_FOR_RESPONSE ? (
        <div className='flex justify-center gap-[10px] w-[100%]'>
          <FriendsButton
            bgcolor='bg-[#10d876]'
            classes='max-w-[80px] h-[42px]'
            onClick={() => handleAnswerRequest('accept')}
          >
            {isLoadingAnswer ? <SpinnerPulse loading={isLoading} /> : t('accept')}
          </FriendsButton>
          <FriendsButton
            bgcolor='bg-red-500'
            classes='max-w-[80px] h-[42px]'
            onClick={() => handleAnswerRequest('reject')}
          >
            {isLoadingAnswer ? <SpinnerPulse loading={isLoading} /> : t('reject')}
          </FriendsButton>
        </div>
      ) : (
        <FriendsButton
          bgcolor={buttonInfo.color}
          classes='max-w-[120px] h-[42px]'
          onClick={!friend_status ? handleAddFriend : null}
          disabled={isLoading}
        >
          {isLoading ? <SpinnerPulse loading={isLoading} /> : buttonInfo.text}
        </FriendsButton>
      )}
    </div>
  );
}

And my apis

import { appApi } from '@/services/api';

export const userApi = appApi.injectEndpoints({
  endpoints: (builder) => ({
    signUpUser: builder.mutation({
      query: ({ fullName, email, password }) => ({
        url: '/user/sign-up',
        method: 'POST',
        body: {
          fullName,
          email,
          password
        }
      })
    }),
    signInUser: builder.mutation({
      query: ({ email, password }) => ({
        url: '/user/sign-in',
        method: 'POST',
        body: {
          email,
          password
        }
      })
    }),
    identifyUser: builder.mutation({
      query: ({ email }) => ({
        url: '/user/send-email',
        method: 'POST',
        body: {
          email
        }
      })
    }),
    resetPassword: builder.mutation({
      query: ({ newPassword, token }) => ({
        url: '/user/reset-password',
        method: 'POST',
        body: {
          newPassword,
          token
        }
      })
    }),
    getUserInfo: builder.mutation({
      query: () => ({
        url: '/user/contact',
        method: 'GET'
      })
    }),
    getAllUsers: builder.query({
      query: ({ page, limit, query }) => ({
        url: `/user/all?page=${page}&limit=${limit}&q=${query}`,
        method: 'GET'
      }),
      providesTags: (result) => {
        return [...result?.users?.map((user) => ({ type: 'User', id: user._id }))];
      }
    })
  })
});

export const {
  useSignUpUserMutation,
  useSignInUserMutation,
  useIdentifyUserMutation,
  useResetPasswordMutation,
  useGetUserInfoMutation,
  useGetAllUsersQuery
} = userApi;

import { appApi } from '@/services/api';
import { userApi } from '@/services/user';
import { FriendStatus } from '@/utils/types/AppTypes';

export const friendsApi = appApi.injectEndpoints({
  endpoints: (builder) => ({
    addFriend: builder.mutation({
      query: ({ id }) => ({
        url: '/friends/add',
        method: 'POST',
        body: {
          to_user_id: id
        }
      }),
      transformResponse: (response, meta, arg) => {
        return arg.id;
      },
      async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
        console.log(id, patch, 'friends');
        const patchResult = dispatch(
          appApi.util.updateQueryData('getAllUsers', { page: 1, limit: 12, q: '' }, (draft) => {
            console.log(draft);
            const user = draft.users.find((user) => user._id === id);
            console.log(user);
            if (user) {
              user.friend_status = FriendStatus.PENDING;
            }
          })
        );
        console.log(patchResult, 'patchResult friends');

        queryFulfilled.catch(patchResult.undo);
      }
      // invalidatesTags: (result, error, { id }) => {
      //   console.log(result, error, id, 'result friends');
      //   return [{ type: 'User', id }];
      // }
    }),
    answerFriendsRequest: builder.mutation({
      query: ({ id, status }) => ({
        url: '/friends/answer',
        method: 'POST',
        body: {
          request_id: id,
          status
        }
      }),
      transformResponse: (response, meta, arg) => {
        return { id: arg.id, status: arg.status };
      }
    })
  })
});

export const { useAddFriendMutation, useAnswerFriendsRequestMutation } = friendsApi;

0

There are 0 answers