Skip to content Skip to sidebar Skip to footer

Failed to Retrieve Mixer Authentication Info! Please Try Again

  • 23 min read
  • React, Apps, Workflow, Techniques, TypeScript

Quick summary ↬ In this article, Georgii Perepecho explains the most mutual React Query features that you lot need to be familiar with when creating a existent-life application that is stable when testing.

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') );        
React Query devtools
React Query devtools. (Large preview)

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);.

A sign-in page and a user name loading.

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.

Deduping requests for the same endpoint.

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.

A load more list.

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:

Background fetching after switching the tab.

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.

The request has been made.
The asking has been made. (Big preview)

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

The request has not been made.
The request has not been made. (Large preview)

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):

  1. Cancel any ongoing requests.
  2. Save the electric current data into a variable.
  3. 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 the updater function.
  4. Render the previous data.

onError (if the request is failed):

  1. Roll back the previous data.

onSettled (if the request is either successful or failed):

  1. 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!]);.

Simple information mutation with invalidation.

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:

  1. User inputs the job name and clicks Add button.
  2. We immediately add together this item to our list and evidence the loader on the Add button.
  3. In parallel we ship a asking to the API.
  4. 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.
  5. 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.
  6. 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]); },        
Optimistic changes with background requests and data invalidation.

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); },        
Optimistic changes and rollback the information due to the error.

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.

Prefetching information if a user moves the cursor into the 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]; });        
Handling the errors by using React Query, Suspense and ErrorBoundary.

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.

  1. We mock requests for Mail service and DELETE.
  2. Input some text in the field and press the button.
  3. Mock Go endpoint again, considering we assume that POST request has been made, and the real server should send us the updated information; in our case, it's a listing with 1 item.
  4. Await for the updated text in the rendered component.
  5. Check that the Post request to api/job has been chosen.
  6. Click the Delete button.
  7. 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).
  8. Bank check that deleted item doesn't be in the document.
  9. Check that the DELETE request to api/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

Smashing Editorial (vf, yk, il)

masonextrave.blogspot.com

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"