Repository: ita-social-projects/SpaceToStudy-Client
Pull request #3354: (SP: ) Replace useAxios with useQuery in ProfileInfo Container #3263
Author: login: danikua
Labels: name: enhancement
description: New feature or request
name: feature
name: useQuery
Created: 2025-04-15T15:22:51Z | Merged: 2025-04-30T13:43:20Z

Replaced `useAxios` with `useQuery` from React Query for fetching chat data. Updated `ProfileInfo` component to use the new query implementation and adjusted tests accordingly.


Diff:
diff --git a/src/containers/offer-page/chat-dialog-window/ChatDialogWindow.tsx b/src/containers/offer-page/chat-dialog-window/ChatDialogWindow.tsx
index 49953298ae..a0e76d0bce 100644
--- a/src/containers/offer-page/chat-dialog-window/ChatDialogWindow.tsx
+++ b/src/containers/offer-page/chat-dialog-window/ChatDialogWindow.tsx
@@ -13,6 +13,7 @@ import ChatTextArea from '~/containers/chat/chat-text-area/ChatTextArea'
 import { useChatContext } from '~/context/chat-context'
 import { useAppDispatch } from '~/hooks/use-redux'
 import useAxios from '~/hooks/use-axios'
+import useQuery from '~/hooks/use-query'
 import AppChip from '~/components/app-chip/AppChip'
 import Message from '~/components/message/Message'
 import UserProfileInfo from '~/components/user-profile-info/UserProfileInfo'
@@ -113,9 +114,12 @@ const ChatDialogWindow: FC<ChatDialogWindow> = ({ chatInfo }) => {
     [chatInfo.author._id, chatInfo.authorRole]
   )
 
-  const { response: listOfChats, loading: isChatsLoading } = useAxios({
-    service: getChats,
-    defaultResponse: defaultResponses.array
+  const { data: listOfChats, isLoading: isChatsLoading } = useQuery({
+    queryKey: ['chats'],
+    queryFn: getChats,
+    options: {
+      staleTime: Infinity
+    }
   })
 
   const {
@@ -184,7 +188,7 @@ const ChatDialogWindow: FC<ChatDialogWindow> = ({ chatInfo }) => {
 
   useEffect(() => {
     if (!isChatsLoading && !messagesLoad) {
-      const thisChat: ChatResponse | undefined = listOfChats.find(
+      const thisChat: ChatResponse | undefined = (listOfChats ?? []).find(
         (chat: ChatResponse) => chat._id === chatInfo.chatId
       ) as unknown as ChatResponse
 
diff --git a/src/containers/user-profile/profile-info/ProfileInfo.tsx b/src/containers/user-profile/profile-info/ProfileInfo.tsx
index aee0207fdc..c980baf8d3 100644
--- a/src/containers/user-profile/profile-info/ProfileInfo.tsx
+++ b/src/containers/user-profile/profile-info/ProfileInfo.tsx
@@ -17,14 +17,14 @@ import ProfileContainerMobile from '~/containers/user-profile/profile-info/Profi
 import { styles } from '~/containers/user-profile/profile-info/ProfileInfo.styles'
 
 import { authRoutes } from '~/router/constants/authRoutes'
-import { defaultResponses, snackbarVariants } from '~/constants'
+import { snackbarVariants } from '~/constants'
 
-import { UserRoleEnum, UserResponse, ChatResponse } from '~/types'
+import { UserRoleEnum, UserResponse } from '~/types'
 import { createUrlPath, getDifferenceDates } from '~/utils/helper-functions'
 import { useAppDispatch } from '~/hooks/use-redux'
 import { openAlert } from '~/redux/features/snackbarSlice'
 import { useChatContext } from '~/context/chat-context'
-import useAxios from '~/hooks/use-axios'
+import useQuery from '~/hooks/use-query'
 import { chatService } from '~/services/chat-service'
 import { DoneItem } from './ProfileInfo.constants'
 
@@ -131,19 +131,25 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery({
+    queryKey: ['chats'],
+    queryFn: chatService.getChats,
+    options: {
+      staleTime: Infinity
+    }
   })
 
-  const onSendMessageClick = () => {
-    const existedChat = listOfChats.find((chat) => {
-      return chat.members.some((member) => member.user._id == userData._id)
-    })
+  const onSendMessageClick = async () => {
+    if (!listOfChats) return
+
+    const existedChat = listOfChats.find((chat) =>
+      chat.members.some((member) => member.user._id === userData._id)
+    )
 
     setChatInfo({
       author: userData,
@@ -153,9 +159,10 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
     })
 
     if (!existedChat?._id) {
-      void fetchData()
+      await refetch()
     }
   }
+
   const buttonGroup = !isMyProfile && (
     <Box sx={styles.buttonGroup}>
       <Button
@@ -173,7 +180,9 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
       <Button
         disabled={isChatsLoading}
         fullWidth
-        onClick={onSendMessageClick}
+        onClick={() => {
+          void onSendMessageClick()
+        }}
         size={isLaptopAndAbove ? 'lg' : 'md'}
       >
         {t('userProfilePage.profileInfo.sendMessage')}
diff --git a/src/pages/chat/Chat.tsx b/src/pages/chat/Chat.tsx
index c858c27ded..9c57272614 100644
--- a/src/pages/chat/Chat.tsx
+++ b/src/pages/chat/Chat.tsx
@@ -8,6 +8,7 @@ import { chatService } from '~/services/chat-service'
 import { messageService } from '~/services/message-service'
 import { useDrawer } from '~/hooks/use-drawer'
 import useAxios from '~/hooks/use-axios'
+import useQuery from '~/hooks/use-query'
 import useBreakpoints from '~/hooks/use-breakpoints'
 import { useAppDispatch, useAppSelector } from '~/hooks/use-redux'
 
@@ -142,12 +143,15 @@ const Chat = () => {
   )
 
   const {
-    fetchData: updateChats,
-    response: listOfChats,
-    loading
-  } = useAxios({
-    service: getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: loading,
+    refetch: updateChats
+  } = useQuery({
+    queryKey: ['chats'],
+    queryFn: getChats,
+    options: {
+      staleTime: Infinity
+    }
   })
 
   const { fetchData, loading: isMessagesLoading } = useAxios({
@@ -178,7 +182,7 @@ const Chat = () => {
     const currentChatId = localStorage.getItem('currentChatId')
 
     if (currentChatId && !selectedChat) {
-      const foundChat = listOfChats.find(
+      const foundChat = (listOfChats ?? []).find(
         (chat: ChatResponse) => chat._id === currentChatId
       )
 
@@ -274,7 +278,7 @@ const Chat = () => {
         >
           <ListOfUsersWithSearch
             closeDrawer={closeDrawer}
-            listOfChats={listOfChats}
+            listOfChats={listOfChats ?? []}
             selectedChat={selectedChat}
             setSelectedChat={handleChatSelection}
           />
@@ -284,7 +288,7 @@ const Chat = () => {
         {!isMobile && (
           <Allotment.Pane minSize={250} preferredSize={350}>
             <ListOfUsersWithSearch
-              listOfChats={listOfChats}
+              listOfChats={listOfChats ?? []}
               selectedChat={selectedChat}
               setSelectedChat={handleChatSelection}
             />
diff --git a/src/services/chat-service.ts b/src/services/chat-service.ts
index 09b344b811..ae9ac397a9 100644
--- a/src/services/chat-service.ts
+++ b/src/services/chat-service.ts
@@ -3,10 +3,14 @@ import { URLs } from '~/constants/request'
 import { axiosClient } from '~/plugins/axiosClient'
 import { BasicChat, ChatResponse } from '~/types'
 import { createUrlPath } from '~/utils/helper-functions'
+import { baseService } from './base-service'
 
 export const chatService = {
-  getChats: (): Promise<AxiosResponse<ChatResponse[]>> => {
-    return axiosClient.get(URLs.chats.get)
+  getChats: () => {
+    return baseService.request<ChatResponse[]>({
+      method: 'GET',
+      url: URLs.chats.get
+    })
   },
   createChat: (
     chatData: BasicChat
diff --git a/tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx b/tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
index 1381bc4200..f041ba1a24 100644
--- a/tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
+++ b/tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
@@ -1,33 +1,26 @@
-import { screen, fireEvent, act } from '@testing-library/react'
-import { renderWithProviders, TestSnackbar } from '~tests/test-utils'
+import { screen, fireEvent, act, waitFor } from '@testing-library/react'
+import {
+  renderWithProviders,
+  TestSnackbar,
+  mockAxiosClient
+} from '~tests/test-utils'
+import { URLs } from '~/constants/request'
 
 import { useMatch } from 'react-router-dom'
 import useBreakpoints from '~/hooks/use-breakpoints'
 import ProfileInfo from '~/containers/user-profile/profile-info/ProfileInfo'
-import useAxios from '~/hooks/use-axios'
 import { vi } from 'vitest'
 
 const mockNavigate = vi.fn()
-
-vi.mock('~/hooks/use-breakpoints')
-
 const mockSetChatInfo = vi.fn()
-const mockFetchData = vi.fn()
 
+vi.mock('~/hooks/use-breakpoints')
 vi.mock('~/context/chat-context', () => ({
   useChatContext: () => ({
     setChatInfo: mockSetChatInfo
   })
 }))
 
-vi.mock('~/hooks/use-axios', () => ({
-  default: vi.fn(() => ({
-    response: [],
-    loading: false,
-    fetchData: vi.fn()
-  }))
-}))
-
 vi.mock('react-router-dom', async () => {
   const actual = await vi.importActual('react-router-dom')
   return {
@@ -96,6 +89,16 @@ const userData = {
   updatedAt: '2023-07-12T19:33:43.616+00:00'
 }
 
+const chatResponse = [
+  {
+    _id: 'chat456',
+    members: [
+      { user: { _id: '64822a1433ebe4890079bb60' } },
+      { user: { _id: 'otherUser' } }
+    ]
+  }
+]
+
 function renderWithBreakpoints(data, role = 'student') {
   useBreakpoints.mockImplementation(() => data)
   renderWithProviders(
@@ -107,7 +110,13 @@ function renderWithBreakpoints(data, role = 'student') {
 
 describe('ProfileInfo component tests', () => {
   beforeEach(() => {
-    vi.resetModules()
+    vi.resetAllMocks()
+    mockAxiosClient.resetHandlers()
+    mockAxiosClient.onGet(URLs.chats.get).reply(200, chatResponse)
+  })
+
+  afterAll(() => {
+    vi.clearAllMocks()
   })
 
   describe('when profile is not own and on laptop', () => {
@@ -116,9 +125,10 @@ describe('ProfileInfo component tests', () => {
       renderWithBreakpoints(laptopData, 'student')
     })
 
-    it('should copy link to profile', async () => {
+    it('should copy link to profile', () => {
       const iconBtn = screen.getByTestId('icon-btn')
-      await act(() => fireEvent.click(iconBtn))
+
+      fireEvent.click(iconBtn)
 
       expect(window.navigator.clipboard.writeText).toHaveBeenCalled()
     })
@@ -127,7 +137,6 @@ describe('ProfileInfo component tests', () => {
       const sendMessageBtn = screen.getByText(
         /userProfilePage.profileInfo.sendMessage/i
       )
-
       expect(sendMessageBtn).toBeInTheDocument()
     })
 
@@ -150,7 +159,6 @@ describe('ProfileInfo component tests', () => {
     it('should copy link to profile', () => {
       const iconBtn = screen.getByTestId('icon-btn')
       fireEvent.click(iconBtn)
-
       expect(window.navigator.clipboard.writeText).toHaveBeenCalled()
     })
 
@@ -158,7 +166,6 @@ describe('ProfileInfo component tests', () => {
       const sendMessageBtn = screen.getByText(
         /userProfilePage.profileInfo.sendMessage/i
       )
-
       expect(sendMessageBtn).toBeInTheDocument()
     })
   })
@@ -171,7 +178,7 @@ describe('ProfileInfo component tests', () => {
       const editIcon = screen.getByTestId('icon-btn').querySelector('svg')
 
       expect(editIcon).toBeInTheDocument()
-      expect(editIcon.getAttribute('data-testid')).toBe('EditOutlinedIcon')
+      expect(editIcon?.getAttribute('data-testid')).toBe('EditOutlinedIcon')
     })
 
     it('should render CopyRoundedIcon for not own profile [ isMyProfile = false ]', () => {
@@ -181,77 +188,41 @@ describe('ProfileInfo component tests', () => {
       const copyIcon = screen.getByTestId('icon-btn').querySelector('svg')
 
       expect(copyIcon).toBeInTheDocument()
-      expect(copyIcon.getAttribute('data-testid')).toBe(
+      expect(copyIcon?.getAttribute('data-testid')).toBe(
         'ContentCopyRoundedIcon'
       )
     })
   })
 })
 
-const chatResponse = [
-  {
-    _id: 'chat456',
-    members: [
-      { user: { _id: '64822a1433ebe4890079bb60' } },
-      { user: { _id: 'otherUser' } }
-    ]
-  }
-]
-
 describe('onSendMessageClick tests', () => {
   beforeEach(() => {
     vi.resetAllMocks()
+    mockAxiosClient.resetHandlers()
   })
 
-  it('should set chat info for an existing chat', () => {
-    useAxios.mockImplementation(() => ({
-      default: vi.fn(),
-      response: chatResponse,
-      loading: false,
-      fetchData: mockFetchData
-    }))
-
-    useMatch.mockImplementation(() => false)
-    renderWithBreakpoints(laptopData, 'student')
-
-    const sendMessageBtn = screen.getByText(
-      /userProfilePage.profileInfo.sendMessage/i
-    )
-    fireEvent.click(sendMessageBtn)
-
-    expect(mockSetChatInfo).toHaveBeenCalledWith({
-      author: userData,
-      authorRole: 'tutor',
-      chatId: 'chat456',
-      updateInfo: expect.any(Function)
-    })
-
-    expect(mockFetchData).not.toHaveBeenCalled()
+  afterAll(() => {
+    vi.clearAllMocks()
   })
 
-  it('should trigger fetchData when no existing chat is found', () => {
-    useAxios.mockImplementation(() => ({
-      default: vi.fn(),
-      response: [],
-      loading: false,
-      fetchData: mockFetchData
-    }))
-
+  it('should set chat info for an existing chat', async () => {
     useMatch.mockImplementation(() => false)
     renderWithBreakpoints(laptopData, 'student')
 
     const sendMessageBtn = screen.getByText(
       /userProfilePage.profileInfo.sendMessage/i
     )
-    fireEvent.click(sendMessageBtn)
-
-    expect(mockSetChatInfo).toHaveBeenCalledWith({
-      author: userData,
-      authorRole: 'tutor',
-      chatId: '',
-      updateInfo: expect.any(Function)
+    await act(async () => {
+      fireEvent.click(sendMessageBtn)
     })
 
-    expect(mockFetchData).toHaveBeenCalled()
+    await waitFor(() => {
+      expect(mockSetChatInfo).toHaveBeenCalledWith({
+        author: userData,
+        authorRole: 'tutor',
+        chatId: 'chat456',
+        updateInfo: expect.any(Function)
+      })
+    })
   })
 })
diff --git a/tests/unit/pages/chat/Chat.spec.jsx b/tests/unit/pages/chat/Chat.spec.jsx
index c4f804a044..f478df10ae 100644
--- a/tests/unit/pages/chat/Chat.spec.jsx
+++ b/tests/unit/pages/chat/Chat.spec.jsx
@@ -1,4 +1,4 @@
-import { fireEvent, screen, waitFor, act } from '@testing-library/react'
+import { fireEvent, screen, act, waitFor } from '@testing-library/react'
 
 import useBreakpoints from '~/hooks/use-breakpoints'
 import Chat from '~/pages/chat/Chat'
@@ -128,10 +128,10 @@ describe('Chat for mobile', () => {
   })
 
   it('should not render left panel in a chat', async () => {
-    const chip = await screen.findByText('chatPage.chat.chipLabel')
-
     await waitFor(() => {
-      expect(chip).not.toBeInTheDocument()
+      expect(
+        screen.queryByText('chatPage.chat.chipLabel')
+      ).not.toBeInTheDocument()
     })
   })
 })


Reviews:
user: login: Iarynovskyi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T12:20:38Z
user: login: danikua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T12:24:36Z
user: login: VKormylo
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T11:04:14Z
user: login: danikua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T09:52:34Z
user: login: danikua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T10:00:13Z
user: login: danikua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T10:02:13Z
user: login: danikua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T10:02:21Z
user: login: Iarynovskyi
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T16:09:23Z
user: login: Mike-Popovych
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T09:19:46Z
user: login: VKormylo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T17:33:30Z
user: login: VKormylo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T17:37:30Z
user: login: VKormylo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T12:59:21Z
user: login: VKormylo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T13:00:50Z
user: login: VKormylo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T13:03:57Z
user: login: VKormylo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T15:14:46Z
user: login: oleg191006
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:47:24Z
user: login: Mike-Popovych
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T13:40:50Z

Review comments:
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Iarynovskyi
body: I think you can move this `async () => {...}` to a separate `const getChats` or something like this for more clean code
created_at: 2025-04-17T12:14:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
+      const res = await chatService.getChats()
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Iarynovskyi
body: And it is minor but i look at services and now with `baseService` you can get data without `return res.data` if you a little rewrite `chatService`, but it is not apply to this task

Only maybe something like this:
```
const getChats = useCallback(() => {
    return chatService.getChats()
})

...
useQuery<ChatResponse[]>({
    queryKey: ['chats'],
    queryFn: getChats,
    },
    options: {
      staleTime: Infinity,
      initialData: defaultResponses.array
    }
...
```

but it is only my think
created_at: 2025-04-17T12:16:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: danikua
body: Actually, I did it at first, but it stopped working
created_at: 2025-04-17T12:24:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: VKormylo
body: You should refactor getChats function from chatService so that it uses baseService. First of all, when replacing useAxios with useQuery we refactor services. Then you can simply use `queryFn: chatService.getChats` or use useCallback if necessary.
created_at: 2025-04-18T11:00:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,7 +104,12 @@ function renderWithBreakpoints(data, role = 'student') {
 
 describe('ProfileInfo component tests', () => {
   beforeEach(() => {
-    vi.resetModules()
+    vi.resetAllMocks()
+    useQuery.mockReturnValue({
path: tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
user: login: VKormylo
body: Try not to mock useQuery, instead use `mockAxiosClient` with onGet and reply to return needed data. You can find examples of that in many test files. It's just better practice to do so.
created_at: 2025-04-18T11:03:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,7 +104,12 @@ function renderWithBreakpoints(data, role = 'student') {
 
 describe('ProfileInfo component tests', () => {
   beforeEach(() => {
-    vi.resetModules()
+    vi.resetAllMocks()
+    useQuery.mockReturnValue({
path: tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
user: login: danikua
body: I'm getting errors when refactoring with `moackAxiosClient`, maybe I should replace it after a full chat refactor with useQuery?
created_at: 2025-04-22T09:52:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: danikua
body: Done
created_at: 2025-04-22T10:00:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: danikua
body: arleady refactored
created_at: 2025-04-22T10:02:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +131,27 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: async () => {
+      const res = await chatService.getChats()
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: danikua
body: done
created_at: 2025-04-22T10:02:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +132,26 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
+  const getAllChats = useCallback(() => chatService.getAllChats(), [])
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: getAllChats,
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Mike-Popovych
body: You can pass just `chatService.getAllChats` to `queryFn` 
created_at: 2025-04-23T09:12:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +132,26 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
+  const getAllChats = useCallback(() => chatService.getAllChats(), [])
+
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Mike-Popovych
body: you can remove it, if you will pass `chatService.getAllChats` to queryFn
created_at: 2025-04-23T09:14:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +132,26 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
+  const getAllChats = useCallback(() => chatService.getAllChats(), [])
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Mike-Popovych
body: ```suggestion
  } = useQuery({
```

created_at: 2025-04-23T09:14:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +132,26 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
+  const getAllChats = useCallback(() => chatService.getAllChats(), [])
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: getAllChats,
+    options: {
+      staleTime: Infinity,
+      initialData: defaultResponses.array
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Mike-Popovych
body: you can remove initial data, because we have a loader
created_at: 2025-04-23T09:18:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,19 +132,26 @@ const ProfileInfo = ({ userData, myRole }: ProfileInfoProps) => {
         description: `${userData.address.city}, ${userData.address.country}`
       }
   ].filter((item): item is DoneItem => !!item)
+
+  const getAllChats = useCallback(() => chatService.getAllChats(), [])
+
   const {
-    response: listOfChats,
-    loading: isChatsLoading,
-    fetchData
-  } = useAxios<ChatResponse[]>({
-    service: chatService.getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: isChatsLoading,
+    refetch
+  } = useQuery<ChatResponse[]>({
+    queryKey: ['chats'],
+    queryFn: getAllChats,
+    options: {
+      staleTime: Infinity,
+      initialData: defaultResponses.array
+    }
   })
 
-  const onSendMessageClick = () => {
-    const existedChat = listOfChats.find((chat) => {
-      return chat.members.some((member) => member.user._id == userData._id)
-    })
+  const onSendMessageClick = async () => {
+    const existedChat = listOfChats.find((chat) =>
+      chat.members.some((member) => member.user._id === userData._id)
+    )
path: src/containers/user-profile/profile-info/ProfileInfo.tsx
user: login: Mike-Popovych
body: ```suggestion
  const onSendMessageClick = async () => {
    const existedChat = listOfChats.find((chat) => {
    if (!listOfChats) {
      return
    }
      chat.members.some((member) => member.user._id === userData._id)
    })
```
created_at: 2025-04-23T09:19:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,11 +3,18 @@ import { URLs } from '~/constants/request'
 import { axiosClient } from '~/plugins/axiosClient'
 import { BasicChat, ChatResponse } from '~/types'
 import { createUrlPath } from '~/utils/helper-functions'
+import { baseService } from './base-service'
 
 export const chatService = {
   getChats: (): Promise<AxiosResponse<ChatResponse[]>> => {
     return axiosClient.get(URLs.chats.get)
   },
+  getAllChats: async (): Promise<ChatResponse[]> => {
path: src/services/chat-service.ts
user: login: VKormylo
body: You don't need to use async/await here, also remove type for the result of the function.
created_at: 2025-04-23T17:33:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,7 +104,12 @@ function renderWithBreakpoints(data, role = 'student') {
 
 describe('ProfileInfo component tests', () => {
   beforeEach(() => {
-    vi.resetModules()
+    vi.resetAllMocks()
+    useQuery.mockReturnValue({
path: tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
user: login: VKormylo
body: What type of errors you get? use something like this:
mockAxiosClient.onGet(URLs.chats.get).reply(200, mockedData)
Also remove useQuery mock if you try to use mockAxiosClient.
created_at: 2025-04-23T17:37:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,33 +1,27 @@
-import { screen, fireEvent, act } from '@testing-library/react'
-import { renderWithProviders, TestSnackbar } from '~tests/test-utils'
+import { screen, fireEvent, act, waitFor } from '@testing-library/react'
+import {
+  renderWithProviders,
+  TestSnackbar,
+  mockAxiosClient
+} from '~tests/test-utils'
+import { URLs } from '~/constants/request'
 
 import { useMatch } from 'react-router-dom'
 import useBreakpoints from '~/hooks/use-breakpoints'
 import ProfileInfo from '~/containers/user-profile/profile-info/ProfileInfo'
-import useAxios from '~/hooks/use-axios'
 import { vi } from 'vitest'
 
 const mockNavigate = vi.fn()
-
-vi.mock('~/hooks/use-breakpoints')
-
 const mockSetChatInfo = vi.fn()
-const mockFetchData = vi.fn()
+const mockRefetch = vi.fn()
path: tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
user: login: VKormylo
body: You don't need this `mockRefetch` here, it won't be called anyway because you haven't mocked useQuery here.
created_at: 2025-04-29T12:59:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,77 +193,45 @@ describe('ProfileInfo component tests', () => {
       const copyIcon = screen.getByTestId('icon-btn').querySelector('svg')
 
       expect(copyIcon).toBeInTheDocument()
-      expect(copyIcon.getAttribute('data-testid')).toBe(
+      expect(copyIcon?.getAttribute('data-testid')).toBe(
         'ContentCopyRoundedIcon'
       )
     })
   })
 })
 
-const chatResponse = [
-  {
-    _id: 'chat456',
-    members: [
-      { user: { _id: '64822a1433ebe4890079bb60' } },
-      { user: { _id: 'otherUser' } }
-    ]
-  }
-]
-
 describe('onSendMessageClick tests', () => {
   beforeEach(() => {
     vi.resetAllMocks()
+    mockAxiosClient.resetHandlers()
   })
 
-  it('should set chat info for an existing chat', () => {
-    useAxios.mockImplementation(() => ({
-      default: vi.fn(),
-      response: chatResponse,
-      loading: false,
-      fetchData: mockFetchData
-    }))
-
-    useMatch.mockImplementation(() => false)
-    renderWithBreakpoints(laptopData, 'student')
-
-    const sendMessageBtn = screen.getByText(
-      /userProfilePage.profileInfo.sendMessage/i
-    )
-    fireEvent.click(sendMessageBtn)
-
-    expect(mockSetChatInfo).toHaveBeenCalledWith({
-      author: userData,
-      authorRole: 'tutor',
-      chatId: 'chat456',
-      updateInfo: expect.any(Function)
-    })
-
-    expect(mockFetchData).not.toHaveBeenCalled()
+  afterAll(() => {
+    vi.clearAllMocks()
   })
 
-  it('should trigger fetchData when no existing chat is found', () => {
-    useAxios.mockImplementation(() => ({
-      default: vi.fn(),
-      response: [],
-      loading: false,
-      fetchData: mockFetchData
-    }))
+  it('should set chat info for an existing chat', async () => {
+    mockAxiosClient.onGet(URLs.chats.get).reply(200, chatResponse)
 
     useMatch.mockImplementation(() => false)
     renderWithBreakpoints(laptopData, 'student')
 
     const sendMessageBtn = screen.getByText(
       /userProfilePage.profileInfo.sendMessage/i
     )
-    fireEvent.click(sendMessageBtn)
+    await act(async () => {
+      fireEvent.click(sendMessageBtn)
+    })
 
-    expect(mockSetChatInfo).toHaveBeenCalledWith({
-      author: userData,
-      authorRole: 'tutor',
-      chatId: '',
-      updateInfo: expect.any(Function)
+    await waitFor(() => {
+      expect(mockSetChatInfo).toHaveBeenCalledWith({
+        author: userData,
+        authorRole: 'tutor',
+        chatId: 'chat456',
+        updateInfo: expect.any(Function)
+      })
     })
 
-    expect(mockFetchData).toHaveBeenCalled()
+    expect(mockRefetch).not.toHaveBeenCalled()
path: tests/unit/containers/tutor-profile/ProfileInfo.spec.jsx
user: login: VKormylo
body: Remove this expect here as well, this mock won't be called by your useQuery. You can replace it with `vi.spyOn` and spy on service function instead.
created_at: 2025-04-29T13:00:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,12 +143,12 @@ const Chat = () => {
   )
 
   const {
-    fetchData: updateChats,
-    response: listOfChats,
-    loading
-  } = useAxios({
-    service: getChats,
-    defaultResponse: defaultResponses.array
+    data: listOfChats,
+    isLoading: loading,
+    refetch: updateChats
+  } = useQuery({
+    queryKey: ['chats'],
+    queryFn: getChats
path: src/pages/chat/Chat.tsx
user: login: VKormylo
body: You should add `staleTime` here as well.
created_at: 2025-04-29T13:03:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-29T15:12:34Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [91.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ita-social-projects_SpaceToStudy-Client&pullRequest=3354)


author_association: NONE