React Query is a powerful tool for handling data fetching, caching, and synchronization in React applications. In this article, we'll create a custom hook using React Query's useIsFetching
, useIsMutating
, and useIsRestoring
functions to determine if any service call is pending, allowing us to manage global loading states and show indicators. Then, we'll write unit tests using Jest to ensure the hook works as expected.
Prerequisites
Before we start, make sure you have the following installed:
- React Query (
@tanstack/react-query
) -
Jest
(for testing) - React Testing Library (
@testing-library/react-hooks
) for testing hooks
If you don’t have these installed, you can add them via npm:
npm install @tanstack/react-query @testing-library/react-hooks jest
Step 1: Creating the Custom Hook
First, let's create a custom hook called useServiceConfig
that checks if any service call is pending:
import { useIsFetching, useIsMutating, useIsRestoring } from '@tanstack/react-query';
import { useMemo } from 'react';
const modes = {
fetching: 'fetching',
mutating: 'mutating',
restoring: 'restoring',
all: 'all',
} as const;
type TMode = keyof typeof modes;
/**
* @name useServiceConfig
* @description A custom hook that returns a boolean value indicating if any service call is pending.
* @param {TMode} mode The mode to check for pending service calls. Default is `'all'`.
* @returns {readonly [boolean]} A tuple containing a single boolean value indicating if any service call is pending.
*/
const useServiceConfig = (mode: TMode = modes.all): readonly [boolean] => {
const isFetching = useIsFetching();
const isMutating = useIsMutating();
const isRestoring = useIsRestoring();
const isPending = useMemo(() => {
switch (mode) {
case modes.fetching:
return isFetching > 0;
case modes.mutating:
return isMutating > 0;
case modes.restoring:
return isRestoring;
case modes.all:
default:
return isFetching > 0 || isMutating > 0 || isRestoring;
}
}, [mode, isFetching, isMutating, isRestoring]);
return [isPending] as const;
};
export default useServiceConfig;
Explanation
-
useIsFetching()
: Returns the number of active queries currently being fetched. -
useIsMutating()
: Returns the number of ongoing mutations (e.g., POST, PUT, DELETE requests). -
useIsRestoring()
: Checks if React Query is restoring the cache from storage.
We combine these values using useMemo
to determine if any of them indicate a pending state. The hook then returns a tuple containing this boolean
value.
We use these functions to determine if any service call is pending. If any of these functions return a value greater than 0, we set isPending
to true
.
Step 2: Writing Unit Tests
Now that we have our hook, let's write unit tests using Jest to ensure it behaves as expected.
Setting Up the Tests
Create a file called useServiceConfig.test.ts
(or .js
if not using TypeScript). We'll use React Testing Library's renderHook
utility to render our hook in a test environment.
import { renderHook } from '@testing-library/react-hooks';
import useServiceConfig from './useServiceConfig';
import { useIsFetching, useIsMutating, useIsRestoring } from '@tanstack/react-query';
jest.mock('@tanstack/react-query');
describe('useServiceConfig', () => {
it('should return false when all statuses are negative or false for the default mode', () => {
(useIsFetching as jest.Mock).mockReturnValue(0);
(useIsMutating as jest.Mock).mockReturnValue(0);
(useIsRestoring as jest.Mock).mockReturnValue(false);
const { result } = renderHook(() => useServiceConfig());
expect(result.current[0]).toBe(false);
});
it('should return true when fetchingStatus is greater than 0 in "fetching" mode', () => {
(useIsFetching as jest.Mock).mockReturnValue(1);
(useIsMutating as jest.Mock).mockReturnValue(0);
(useIsRestoring as jest.Mock).mockReturnValue(false);
const { result } = renderHook(() => useServiceConfig('fetching'));
expect(result.current[0]).toBe(true);
});
it('should return true when mutatingStatus is greater than 0 in "mutating" mode', () => {
(useIsFetching as jest.Mock).mockReturnValue(0);
(useIsMutating as jest.Mock).mockReturnValue(1);
(useIsRestoring as jest.Mock).mockReturnValue(false);
const { result } = renderHook(() => useServiceConfig('mutating'));
expect(result.current[0]).toBe(true);
});
it('should return true when restoringStatus is true in "restoring" mode', () => {
(useIsFetching as jest.Mock).mockReturnValue(0);
(useIsMutating as jest.Mock).mockReturnValue(0);
(useIsRestoring as jest.Mock).mockReturnValue(true);
const { result } = renderHook(() => useServiceConfig('restoring'));
expect(result.current[0]).toBe(true);
});
it('should return true when any status is positive or true in "all" mode', () => {
(useIsFetching as jest.Mock).mockReturnValue(1);
(useIsMutating as jest.Mock).mockReturnValue(0);
(useIsRestoring as jest.Mock).mockReturnValue(false);
const { result } = renderHook(() => useServiceConfig('all'));
expect(result.current[0]).toBe(true);
});
});
Explanation of the Tests
-
Mocking Dependencies
:- We use
jest.mock
to mock the functionsuseIsFetching
,useIsMutating
, anduseIsRestoring
. - Mocking allows us to simulate different return values and control the behavior during tests.
- We use
-
Test Cases
:-
Default Mode
:- (
'all'
): If all statuses arezero
orfalse
, the hook should returnfalse
.
- (
-
Specific Modes
:- 'fetching': If useIsFetching returns a value greater than 0, the hook should return true.
- 'mutating': If useIsMutating returns a value greater than 0, the hook should return true.
- 'restoring': If useIsRestoring returns true, the hook should also return true.
-
-
Running the Tests
:-
Run the tests using Jest:
npm test
You should see output indicating all tests have passed.
-
Conclusion
In this article, we built a custom React Query hook that checks the status of service calls based on different modes (fetching
, mutating
, restoring
, or all
). We then wrote and ran tests using Jest to ensure our hook behaves correctly in various scenarios. This approach helps manage global loading states, making it easier to show indicators in your application.
By following these steps, you can create similar hooks for different use cases and confidently test them.
Next Steps
- Try extending the hook to accept other parameters, such as specific query keys, to customize its behavior further.
- Explore more of React Query’s utilities to enhance your application's performance and user experience.
Happy coding!