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;