Failed to Retrieve Mixer Authentication Info! Please Try Again
If y'all accept always congenital React applications that use asynchronous data you probably know how annoying it could be to handle different states (loading, error, and and then on), share the land between components using the same API endpoint, and continue the synchronized state in your components.
In social club to refresh data, we should practise a lot of actions: define useState
and useEffect
hooks, fetch data from the API, put the updated data to the state, change the loading land, handle errors, and and then on. Fortunately, we have React Query, i.east. a library that makes fetching, caching, and managing the data easier.
Benefits Of Using The New Approach
React Query has an impressive list of features:
- caching;
- deduping multiple requests for the aforementioned data into a single request;
- updating "out of date" data in the background (on windows focus, reconnect, interval, and so on);
- functioning optimizations like pagination and lazy loading data;
- memoizing query results;
- prefetching the data;
- mutations, which arrive easy to implement optimistic changes.
To demonstrate these features I've implemented an case application, where I tried to cover all cases for those you would like to use React Query. The awarding is written in TypeScript and uses CRA, React query, Axios mock server and material UI for easier prototyping.
Demonstration The Example Awarding
Let's say nosotros would like to implement the car service system. It should be able to:
- log in using electronic mail and countersign and indicate the logged user;
- evidence the list of next appointments with load more feature;
- prove data about one particular date;
- save and view changes history;
- prefetch additional information;
- add and amend required jobs.
More after jump! Continue reading below ↓
Client-Side Interaction
Every bit nosotros don't have a real backend server, we will use axios-mock-adapter
. I prepared some kind of REST API
with get/postal service/patch/delete endpoints. To store information, we will use fixtures. Nothing special — just variables which we will mutate.
Too, in order to be able to view state changes, I've set the delay fourth dimension as 1 second per asking.
Preparing React Query For Using
Now we are ready to set up React Query. It'south pretty straightforward.
First, we have to wrap our app with the provider:
const queryClient = new QueryClient(); ReactDOM.render( <React.StrictMode> <Router> <QueryClientProvider client={queryClient}> <App /> <ToastContainer /> </QueryClientProvider> </Router> </React.StrictMode>, certificate.getElementById('root') );
In QueryClient()
, nosotros could specify some global defaults.
For easier development, nosotros will create our own abstractions for React Query hooks. To exist able to subscribe to a query nosotros have to pass a unique key. The easiest way to use strings, only it'southward possible to employ array-like keys.
In the official documentation, they utilise string keys, but I institute it a bit redundant equally nosotros already have URLs for calling API requests. So, we could utilise the URL every bit a fundamental, and then that we don't demand to create new strings for keys.
However there are some restrictions: if you are going to use dissimilar URLs for GET/PATCH
, for example, yous have to utilize the aforementioned key, otherwise, React Query will not be able to match these queries.
Also, nosotros should keep in heed that information technology'due south important to include not just the URL simply besides all parameters which we are going to apply to make requests to the backend. A combination of URL and params will create a solid key which the React Query will use for caching.
As a fetcher, we volition utilise Axios where nosotros pass a URL and params from queryKey
.
consign const useFetch = <T>( url: string | null, params?: object, config?: UseQueryOptions<T, Error, T, QueryKeyT> ) => { const context = useQuery<T, Error, T, QueryKeyT>( [url!, params], ({ queryKey }) => fetcher({ queryKey }), { enabled: !!url, ...config, } ); return context; }; consign const fetcher = <T>({ queryKey, pageParam, }: QueryFunctionContext<QueryKeyT>): Hope<T> => { const [url, params] = queryKey; render api .go<T>(url, { params: { ...params, pageParam } }) .then((res) => res.data); };
Where [url!, params]
is our fundamental, setting enabled: !!url
we utilize for pausing requests if in that location is no fundamental (I'll talk about that a scrap afterwards). For fetcher we could use anything — information technology doesn't matter. For this case, I chose Axios.
For a smoother programmer experience, it's possible to use React Query Devtools by adding information technology to the root component.
import { ReactQueryDevtools } from 'react-query/devtools'; ReactDOM.return( <React.StrictMode> <Router> <QueryClientProvider client={queryClient}> <App /> <ToastContainer /> <ReactQueryDevtools initialIsOpen={fake} /> </QueryClientProvider> </Router> </React.StrictMode>, document.getElementById('root') );

Overnice one!
Authentication
To be able to apply our app, nosotros should log in by entering the email and password. The server returns the token and we store it in cookies (in the example app any combination of email/countersign works). When a user goes around our app we attach the token to each request.
Also, nosotros fetch the user profile by the token. On the header, we testify the user name or the loading if the asking is notwithstanding in progress. The interesting part is that we tin handle a redirect to the login page in the root App
component, merely testify the user proper name in the separate component.
This is where the React Query magic starts. By using hooks, we could easily share data about a user without passing it as props.
App.tsx
:
const { mistake } = useGetProfile(); useEffect(() => { if (error) { history.supercede(pageRoutes.auth); } }, [mistake]);
UserProfile.tsx
:
const UserProfile = ({}: Props) => { const { data: user, isLoading } = useGetProfile(); if (isLoading) { return ( <Box display="flex" justifyContent="flex-end"> <CircularProgress color="inherit" size={24} /> </Box> ); } return ( <Box brandish="flex" justifyContent="flex-cease"> {user ? `User: ${user.proper noun}` : 'Unauthorized'} </Box> ); };
And the request to the API will be called simply once (it is called deduping requests, and I'll talk well-nigh it a bit more in the next section).
Hook to fetch the profile information:
export const useGetProfile = () => { const context = useFetch<{ user: ProfileInterface }>( apiRoutes.getProfile, undefined, { retry: false } ); return { ...context, information: context.information?.user }; };
We utilise the retry: simulated
setting here because we don't want to retry this request. If it fails, we believe that the user is unauthorized and practise the redirect.
When users enter their login and password nosotros send a regular Post
asking. Theoretically, we could use React Query mutations here, simply in this case, we don't need to specify const [btnLoading, setBtnLoading] = useState(false);
state and manage information technology, but I think information technology would exist unclear and probably over complicated in this detail case.
If the request is successful, we invalidate all queries to go fresh data. In our app it would exist just ane query: user profile to update the proper noun in the header, but only to be sure we invalidate everything.
if (resp.data.token) { Cookies.fix('token', resp.data.token); history.supplant(pageRoutes.principal); queryClient.invalidateQueries(); }
If we wanted to invalidate just a single query nosotros would use queryClient.invalidateQueries(apiRoutes.getProfile);
.
More About Deduping Requests
Let's assume that we have two different (or even the same) components on the page which utilise the same API endpoint. Ordinarily, we would accept to make two requests, which are actually the same, and it's merely a waste of the backend resources. Using React Query, we could dedupe API calls with the same params. This ways that if nosotros have components that phone call the same requests, the asking will be made just one time.
In our app, nosotros have two components: showing total appointments and the list.
Total appointments component:
const UsersSummary = () => { const { data: list, isLoading } = useGetAppointmentsList(); if (!isLoading && !listing) { return null; } return ( <Box mb={ii}> <Card> <Box p={two}> <Typography> Total appointments:{' '} {isLoading ? ( <Skeleton animation="wave" variant="rectangular" acme={15} width="lx%" /> ) : ( list!.pages[0].count )} </Typography> </Box> </Carte du jour> </Box> ); };
Users list component:
const UsersList = () => { const { data: list, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage, } = useGetAppointmentsList(); render ( <> <Card> {isLoading ? ( <List> <Box mb={1}> <UserItemSkeleton /> </Box> <Box mb={1}> <UserItemSkeleton /> </Box> <Box mb={ane}> <UserItemSkeleton /> </Box> </List> ) : ( <List> {list!.pages.map((folio) => ( <React.Fragment key={folio.nextId || 0}> {page.information.map((particular) => ( <UserItem key={item.id} id={item.id} name={detail.proper name} appointment={particular.appointment_date} /> ))} </React.Fragment> ))} </List> )} </Card> {hasNextPage && ( <Box mt={two}> <Button variant="contained" color="primary" onClick={() => { fetchNextPage(); }} disabled={isFetchingNextPage} > {isFetchingNextPage ? 'Loading more...' : 'Load more users'} </Push button> </Box> )} </> ); };
They use the same hook useGetAppointmentsList()
where we transport the request to the API. As nosotros could come across, the request Get /api/getUserList
was called but once.
Load More than List
In our app, we have an infinite list with a Load more
push. We cannot implement that using a regular useQuery
hook, that'southward why we have useInfiniteQuery
hook, it makes information technology possible to handle pagination, using fetchNextPage
function.
consign const useGetAppointmentsList = () => useLoadMore<AppointmentInterface[]>(apiRoutes.getUserList);
We have our own abstraction for the React Query hook:
export const useLoadMore = <T>(url: string | null, params?: object) => { const context = useInfiniteQuery< GetInfinitePagesInterface<T>, Error, GetInfinitePagesInterface<T>, QueryKeyT >( [url!, params], ({ queryKey, pageParam = ane }) => fetcher({ queryKey, pageParam }), { getPreviousPageParam: (firstPage) => firstPage.previousId ?? faux, getNextPageParam: (lastPage) => { return lastPage.nextId ?? false; }, } ); return context; };
Information technology's pretty much the aforementioned that we have for useFetch
hook, merely hither we specify getPreviousPageParam
and getNextPageParam
functions, based on the API response, and also we pass pageParam
property to the fetcher role.
const UsersList = () => { const { data: list, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage, } = useGetAppointmentsList(); render ( <> <Card> {isLoading ? ( <List> <Box mb={1}> <UserItemSkeleton /> </Box> <Box mb={1}> <UserItemSkeleton /> </Box> <Box mb={1}> <UserItemSkeleton /> </Box> </Listing> ) : ( <List> {listing!.pages.map((folio) => ( <React.Fragment cardinal={page.nextId || 0}> {folio.information.map((item) => ( <UserItem key={detail.id} id={item.id} name={item.name} date={item.appointment_date} /> ))} </React.Fragment> ))} </List> )} </Card> {hasNextPage && ( <Box mt={2}> <Push button variant="contained" colour="main" onClick={() => { fetchNextPage(); }} disabled={isFetchingNextPage} > {isFetchingNextPage ? 'Loading more...' : 'Load more than users'} </Button> </Box> )} </> ); };
useInfiniteQuery
claw has several additional fields similar fetchNextPage
, hasNextPage
, isFetchingNextPage
, which we could utilise to handle our load more than list. And methods fetchNextPage
, fetchPreviousPage
.
Groundwork Fetching Indicator/Refetching
Ane of the nearly interesting features for me is refetching data if nosotros alter window focus, similar switching between tabs. For example, information technology could be useful if data could be changed past several authors. In this case, if we go on the browser tab opened we don't have to reload the page. We volition meet the actual information when we focus on the window. Moreover, nosotros could use a flag, to indicate that fetching is in progress. React Query has several settings in case you lot don't demand it:
-
refetchInterval
, -
refetchIntervalInBackground
, -
refetchOnMount
, -
refetchOnReconnect
, -
refetchOnWindowFocus
.
Besides it'due south possible to disable/enable options globally:
const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, }, }, });
To indicate re-fetching nosotros have isFetching
flag to show loading state:
Making Conditional Requests
Equally we use hooks to fetch the information it could be disruptive how we can avoid making requests. As you know, we cannot use conditional statements with hooks, for example nosotros cannot lawmaking like that:
if (data?.hasInsurance) { const { information: insurance } = useGetInsurance( data?.hasInsurance ? +id : goose egg ); }
Allow's assume in our app we should brand an additional request to get insurance details, based on the appointment endpoint response.
If we want to make a request, nosotros pass a fundamental, otherwise we laissez passer nothing.
const { data: insurance } = useGetInsurance(data?.hasInsurance ? +id : null); consign const useGetInsurance = (id: number | zip) => useFetch<InsuranceDetailsInterface>( id ? pathToUrl(apiRoutes.getInsurance, { id }) : null );
In our useFetch
abstraction, nosotros set enabled the belongings in the config as simulated in case we don't take a primal. In this case, React Query simply pauses making requests.
For an appointment with id = ane
, we have hasInsurance = true
. Next, we make another asking and testify a bank check icon next to the name. This means that nosotros received an allCovered
flag from the getInsurance
endpoint.

For an appointment with id = 2
we have hasInsurance = false
, and we don't make requests for the insurance details.

Elementary Mutation With Information Invalidation
To create/update/delete data in React Query we use mutations. It means we send a asking to the server, receive a response, and based on a defined updater function we mutate our state and keep information technology fresh without making an boosted request.
We accept a genetic abstraction for these actions.
const useGenericMutation = <T, S>( func: (information: S) => Promise<AxiosResponse<Southward>>, url: string, params?: object, updater?: ((oldData: T, newData: S) => T) | undefined ) => { const queryClient = useQueryClient(); return useMutation<AxiosResponse, AxiosError, S>(func, { onMutate: async (data) => { look queryClient.cancelQueries([url!, params]); const previousData = queryClient.getQueryData([url!, params]); queryClient.setQueryData<T>([url!, params], (oldData) => { return updater ? (oldData!, data) : data; }); return previousData; }, // If the mutation fails, apply the context returned from onMutate to roll back onError: (err, _, context) => { queryClient.setQueryData([url!, params], context); }, onSettled: () => { queryClient.invalidateQueries([url!, params]); }, }); };
Let'due south have a look in more detail. We take several callback
methods:
onMutate
(if the request is successful):
- Cancel any ongoing requests.
- Save the electric current data into a variable.
- If divers, we utilize an
updater
function to mutate our land by some specific logic, if not, only override the state with the new data. In most cases, information technology makes sense to define theupdater
function. - Render the previous data.
onError
(if the request is failed):
- Roll back the previous data.
onSettled
(if the request is either successful or failed):
- Invalidate the query to continue the fresh state.
This abstraction we will use for all mutation actions.
consign const useDelete = <T>( url: string, params?: object, updater?: (oldData: T, id: string | number) => T ) => { render useGenericMutation<T, string | number>( (id) => api.delete(`${url}/${id}`), url, params, updater ); }; export const usePost = <T, S>( url: string, params?: object, updater?: (oldData: T, newData: S) => T ) => { return useGenericMutation<T, S>( (data) => api.post<South>(url, information), url, params, updater ); }; export const useUpdate = <T, Southward>( url: cord, params?: object, updater?: (oldData: T, newData: S) => T ) => { return useGenericMutation<T, South>( (information) => api.patch<S>(url, data), url, params, updater ); };
That's why information technology's very important to have the same set of [url!, params]
(which we use as a key) in all hooks. Without that the library volition not be able to invalidate the state and lucifer the queries.
Allow's see how information technology works in our app: we have a History
section, clicking by Salve
button nosotros send a PATCH
request and receive the whole updated date object.
First, nosotros define a mutation. For now, we are not going to perform whatever complex logic, just returning the new state, that's why we are not specifying the updater
role.
const mutation = usePatchAppointment(+id); consign const usePatchAppointment = (id: number) => useUpdate<AppointmentInterface, AppointmentInterface>( pathToUrl(apiRoutes.appointment, { id }) );
Note: It uses our generic useUpdate
hook.
Finally, we call the mutate
method with the data we want to patch: mutation.mutate([data!]);
.
Note: In this component, nosotros employ an isFetching
flag to indicate updating data on window focus (bank check Background
fetching section), so, we show the loading state each time when the asking is in-flight. That's why when we click Save
, mutate the land and fetch the actual response nosotros show the loading state as well. Ideally, it shouldn't exist shown in this case, just I haven't constitute a style to indicate a groundwork fetching, but don't indicate fetching when loading the fresh data.
const History = ({ id }: Props) => { const { data, isFetching } = useGetAppointment(+id); const mutation = usePatchAppointment(+id); if (isFetching) { render ( <Box> <Box pt={2}> <Skeleton animation="wave" variant="rectangular" top={fifteen} /> </Box> <Box pt={2}> <Skeleton animation="wave" variant="rectangular" height={15} /> </Box> <Box pt={ii}> <Skeleton animation="wave" variant="rectangular" height={xv} /> </Box> </Box> ); } const onSubmit = () => { mutation.mutate(data!); }; render ( <> {data?.history.map((item) => ( <Typography variant="body1" cardinal={item.date}> Engagement: {item.engagement} <br /> Comment: {item.comment} </Typography> ))} {!information?.history.length && !isFetching && ( <Box mt={ii}> <span>Nix found</span> </Box> )} <Box mt={3}> <Button variant="outlined" color="main" size="big" onClick={onSubmit} disabled={!data || mutation.isLoading} > Save </Push> </Box> </> ); };
Mutation With Optimistic Changes
Now let's have a await at the more complex example: in our app, we desire to accept a list, where we should be able to add together and remove items. Too, we want to brand the user feel as smooth as nosotros can. We are going to implement optimistic changes for creating/deleting jobs.
Here are the deportment:
- User inputs the job name and clicks
Add
button. - We immediately add together this item to our list and evidence the loader on the
Add
button. - In parallel we ship a asking to the API.
- When the response is received we hide the loader, and if information technology succeeds we just go along the previous entry, update its id in the list, and clear the input field.
- If the response is failed we show the error notification, remove this particular from the listing, and keep the input field with the former value.
- In both cases we send
Go
request to the API to brand sure we have the actual state.
All our logic is:
const { data, isLoading } = useGetJobs(); const mutationAdd = useAddJob((oldData, newData) => [...oldData, newData]); const mutationDelete = useDeleteJob((oldData, id) => oldData.filter((item) => item.id !== id) ); const onAdd = async () => { try { await mutationAdd.mutateAsync({ name: jobName, appointmentId, }); setJobName(''); } catch (e) { pushNotification(`Cannot add the job: ${jobName}`); } }; const onDelete = async (id: number) => { endeavour { wait mutationDelete.mutateAsync(id); } catch (eastward) { pushNotification(`Cannot delete the job`); } };
In this example nosotros define our own updater
functions to mutate the land by custom logic: for united states, it's just creating an array with the new item and filtering by id
if we want to delete the particular. Only the logic could be any, it depends on your tasks.
React Query takes care of changing states, making requests, and rolling back the previous land if something goes incorrect.
In the console you could see which requests axios makes to our mock API. We could immediately meet the updated list in the UI, and then nosotros call POST
and finally we telephone call Get
. It works considering nosotros defined onSettled
callback in useGenericMutation
hook, and so later success or fault nosotros always refetch the data:
onSettled: () => { queryClient.invalidateQueries([url!, params]); },
Notation: When I highlight the lines in the dev tools you could see a lot of made requests. This is because we modify the window focus when we click on the Dev Tools window, and React Query invalidates the land.
If the backend returned the mistake, we would rollback the optimistic changes, and evidence the notification. Information technology works because we defined onError
callback in useGenericMutation
hook, so we set previous information if an error happened:
onError: (err, _, context) => { queryClient.setQueryData([url!, params], context); },
Prefetching
Prefetching could be useful if we want to have the data in accelerate and if there is a loftier possibility that a user will request this data in the near futurity.
In our case, we will prefetch the motorcar details if the user moves the mouse cursor in the Boosted
section area.
When the user clicks the Show
button we will return the information immediately, without calling the API (despite having a 1-2d delay).
const prefetchCarDetails = usePrefetchCarDetails(+id); onMouseEnter={() => { if (!prefetched.current) { prefetchCarDetails(); prefetched.current = true; } }} export const usePrefetchCarDetails = (id: number | null) => usePrefetch<InsuranceDetailsInterface>( id ? pathToUrl(apiRoutes.getCarDetail, { id }) : nil );
We have our brainchild claw for the prefetching:
consign const usePrefetch = <T>(url: string | zero, params?: object) => { const queryClient = useQueryClient(); return () => { if (!url) { return; } queryClient.prefetchQuery<T, Error, T, QueryKeyT>( [url!, params], ({ queryKey }) => fetcher({ queryKey }) ); }; };
To render the car details we use CarDetails
component, where we ascertain a claw to retrieve data.
const CarDetails = ({ id }: Props) => { const { information, isLoading } = useGetCarDetail(id); if (isLoading) { render <CircularProgress />; } if (!data) { return <span>Nil constitute</bridge>; } render ( <Box> <Box mt={2}> <Typography>Model: {information.model}</Typography> </Box> <Box mt={2}> <Typography>Number: {information.number}</Typography> </Box> </Box> ); }; export const useGetCarDetail = (id: number | null) => useFetch<CarDetailInterface>( pathToUrl(apiRoutes.getCarDetail, { id }), undefined, { staleTime: 2000 } );
Good point that we don't take to pass boosted props to this component, then in the Date
component we prefetch the data and in the CarDetails
component we use useGetCarDetail
hook to call up the prefetched data.
By setting extended staleTime
, we let users to spend a chip more time earlier they click on the Evidence
push button. Without this setting, the request could be called twice if it takes likewise long between moving the cursor on the prefetching area and clicking the button.
Suspense
Suspense is an experimental React feature that makes it possible to look for some code in a declarative way. In other words, we could call the Suspense component and define the fallback
component, which we want to show while we are waiting for the data. We don't fifty-fifty need the isLoading
flag from React Query. For more information please refer to the official documentation.
Permit's say nosotros have a Service
list, and we want to evidence the mistake, and Endeavour again
push if something went wrong.
To get the new programmer experience permit's utilise Suspense, React Query and Fault Boundaries together. For the final ane, we will use react-mistake-boundary
Library.
<QueryErrorResetBoundary> {({ reset }) => ( <ErrorBoundary fallbackRender={({ error, resetErrorBoundary }) => ( <Box width="100%" mt={2}> <Alert severity="fault"> <AlertTitle> <strong>Error!</strong> </AlertTitle> {error.message} </Alert> <Box mt={2}> <Push button variant="independent" color="error" onClick={() => resetErrorBoundary()} > Try again </Button> </Box> </Box> )} onReset={reset} > <React.Suspense fallback={ <Box width="100%"> <Box mb={1}> <Skeleton variant="text" animation="wave" /> </Box> <Box mb={ane}> <Skeleton variant="text" animation="wave" /> </Box> <Box mb={1}> <Skeleton variant="text" animation="wave" /> </Box> </Box> } > <ServicesCheck checked={checked} onChange={onChange} /> </React.Suspense> </ErrorBoundary> )} </QueryErrorResetBoundary>
Within the Suspense component, we return our ServiceCheck
component, where we telephone call the API endpoint for the service list.
const { data } = useGetServices();
In the hook, nosotros ready suspense: true
and retry: 0
.
export const useGetServices = () => useFetch<ServiceInterface[]>(apiRoutes.getServices, undefined, { suspense: truthful, retry: 0, });
On the mock server, we send a response of either 200
or 500
status codes randomly.
mock.onGet(apiRoutes.getServices).reply((config) => { if (!getUser(config)) { render [403]; } const failed = !!Math.round(Math.random()); if (failed) { return [500]; } return [200, services]; });
So, if we receive some error from the API, and we don't handle it, we prove the carmine notification with the message from the response. Clicking on the Try over again
push we call resetErrorBoundary()
method, which tries to call the asking again. In React Suspense fallback, we take our loading skeleton component, which renders when we are making the requests.
As we could see, this is a convenient and piece of cake way to handle async data, just keep in listen that this is unstable, and probably shouldn't be used in production right now.
Testing
Testing applications using React Query is almost the same equally testing a regular application. We will use React Testing Library and Jest.
Beginning, we create an brainchild for the rendering components.
export const renderComponent = (children: React.ReactElement, history: any) => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); const options = render( <Router history={history}> <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> </Router> ); return { ...options, debug: ( el?: HTMLElement, maxLength = 300000, opt?: prettyFormat.OptionsReceived ) => options.debug(el, maxLength, opt), }; };
We set retry: simulated
as a default setting in QueryClient
and wrap a component with QueryClientProvider
.
Now, let's examination our Date
component.
Nosotros start with the easiest one: just checking that the component renders correctly.
examination('should render the primary page', async () => { const mocked = mockAxiosGetRequests({ '/api/appointment/1': { id: 1, name: 'Hector Mckeown', appointment_date: '2021-08-25T17:52:48.132Z', services: [1, ii], accost: 'London', vehicle: 'FR14ERF', comment: 'Machine does not work correctly', history: [], hasInsurance: true, }, '/api/job': [], '/api/getServices': [ { id: 1, name: 'Replace a cambelt', }, { id: 2, proper noun: 'Replace oil and filter', }, { id: 3, proper noun: 'Supervene upon front end brake pads and discs', }, { id: 4, name: 'Replace rare brake pads and discs', }, ], '/api/getInsurance/1': { allCovered: true, }, }); const history = createMemoryHistory(); const { getByText, queryByTestId } = renderComponent( <Appointment />, history ); expect(queryByTestId('date-skeleton')).toBeInTheDocument(); await waitFor(() => { expect(queryByTestId('appointment-skeleton')).not.toBeInTheDocument(); }); look(getByText('Hector Mckeown')).toBeInTheDocument(); expect(getByText('Replace a cambelt')).toBeInTheDocument(); expect(getByText('Replace oil and filter')).toBeInTheDocument(); look(getByText('Replace front brake pads and discs')).toBeInTheDocument(); expect(queryByTestId('DoneAllIcon')).toBeInTheDocument(); expect( mocked.mock.calls.some((item) => item[0] === '/api/getInsurance/1') ).toBeTruthy(); });
We have prepared helpers to mock Axios requests. In the tests, we could specify URL and mock data.
const getMockedData = ( originalUrl: string, mockData: { [url: cord]: any }, type: cord ) => { const foundUrl = Object.keys(mockData).discover((url) => originalUrl.friction match(new RegExp(`${url}$`)) ); if (!foundUrl) { return Promise.reject( new Fault(`Chosen unmocked api ${type} ${originalUrl}`) ); } if (mockData[foundUrl] instanceof Fault) { return Hope.reject(mockData[foundUrl]); } render Promise.resolve({ data: mockData[foundUrl] }); }; export const mockAxiosGetRequests = <T extends whatever>(mockData: { }): MockedFunction<AxiosInstance> => { // @ts-ignore return axios.go.mockImplementation((originalUrl) => getMockedData(originalUrl, mockData, 'GET') ); };
Then, nosotros check there is a loading state and next, look for the unmounting of the loading component.
expect(queryByTestId('appointment-skeleton')).toBeInTheDocument(); await waitFor(() => { look(queryByTestId('appointment-skeleton')).non.toBeInTheDocument(); });
Adjacent, we check that in that location are necessary texts in the rendered component, and finally check that the API request for the insurance details has been called.
expect( mocked.mock.calls.some((item) => item[0] === '/api/getInsurance/1') ).toBeTruthy();
It checks that loading flags, fetching data and calling endpoints work correctly.
In the next text, we bank check that we do not call asking for the insurance details if we don't need it (remember in the component we have a status, that if in the response from date endpoint at that place is a flag hasInsurance: true
we should call the insurance endpoint, otherwise we shouldn't).
exam('should not call and render Insurance flag', async () => { const mocked = mockAxiosGetRequests({ '/api/appointment/1': { id: one, proper noun: 'Hector Mckeown', appointment_date: '2021-08-25T17:52:48.132Z', services: [1, ii], address: 'London', vehicle: 'FR14ERF', annotate: 'Car does not work correctly', history: [], hasInsurance: false, }, '/api/getServices': [], '/api/job': [], }); const history = createMemoryHistory(); const { queryByTestId } = renderComponent(<Engagement />, history); await waitFor(() => { await(queryByTestId('appointment-skeleton')).not.toBeInTheDocument(); }); expect(queryByTestId('DoneAllIcon')).not.toBeInTheDocument(); expect( mocked.mock.calls.some((item) => item[0] === '/api/getInsurance/one') ).toBeFalsy(); });
This test checks that if we accept hasInsurance: imitation
in the response, we volition non call the insurance endpoint and render the icon.
Last, we are going to test mutations in our Jobs
component. The whole test example:
test('should be able to add together and remove elements', async () => { const mockedPost = mockAxiosPostRequests({ '/api/job': { proper name: 'First detail', appointmentId: 1, }, }); const mockedDelete = mockAxiosDeleteRequests({ '/api/job/one': {}, }); const history = createMemoryHistory(); const { queryByTestId, queryByText } = renderComponent( <Jobs appointmentId={1} />, history ); await waitFor(() => { wait(queryByTestId('loading-skeleton')).non.toBeInTheDocument(); }); look changeTextFieldByTestId('input', 'Offset item'); await clickByTestId('add'); mockAxiosGetRequests({ '/api/task': [ { id: 1, name: 'First particular', appointmentId: ane, }, ], }); await waitFor(() => { expect(queryByText('First detail')).toBeInTheDocument(); }); expect( mockedPost.mock.calls.some((item) => detail[0] === '/api/job') ).toBeTruthy(); await clickByTestId('delete-1'); mockAxiosGetRequests({ '/api/job': [], }); await waitFor(() => { expect(queryByText('First item')).not.toBeInTheDocument(); }); await( mockedDelete.mock.calls.some((item) => detail[0] === '/api/chore/1') ).toBeTruthy(); });
Let's see what is happening here.
- We mock requests for
Mail service
andDELETE
. - Input some text in the field and press the button.
- Mock
Go
endpoint again, considering we assume thatPOST
request has been made, and the real server should send us the updated information; in our case, it's a listing with 1 item. - Await for the updated text in the rendered component.
- Check that the
Post
request toapi/job
has been chosen. - Click the
Delete
button. - Mock
GET
endpoint once more with an empty list (similar in the previous case we assume the server sent united states of america the updated information after deleting). - Bank check that deleted item doesn't be in the document.
- Check that the
DELETE
request toapi/job/one
has been called.
Important Note: Nosotros need to clear all mocks later each test to avoid mixing them upward.
afterEach(() => { jest.clearAllMocks(); });
Conclusion
With the assistance of this existent-life application, we went through all of the most common React Query features: how to fetch data, manage states, share between components, make it easier to implement optimistic changes and space lists, and learned how to make the app stable with tests.
I hope I could involvement you in trying out this new arroyo in your electric current or upcoming projects.
Resources
- Instance app used in the article
- Official documentation
-
Axios-mock-adapter
(vf, yk, il)
Source: https://www.smashingmagazine.com/2022/01/building-real-app-react-query/
Enregistrer un commentaire for "Failed to Retrieve Mixer Authentication Info! Please Try Again"