React Query
Introduction
The library to handle data fetching, including retry, caching, error handling, so as to reduce the boilerplate code
Convenient to create custom hook to fetching or updating data in order to achieve separate of concern
Fresh & Stale State
Fresh: all the data must come from cache, no background refetching will be happened
Stale: data can still come from cache, but the background refetching can be happened
Default Behavior
Stale queries are refetched automatically in the background when:
New instances of the query mount
The window is refocused
The network is reconnected.
The query is optionally configured with a refetch interval.
If Query fails, will silently retry 3 times
The data of the inactive queries will be cached for 5 mins
The staleTime is 0
isLoading
: Your query has no data and is currently loading for the first timeisFetching
flag is not part of the internal state machine - it is an additional flag that will be true whenever a request is in-flight. You can be fetching and success, you can be fetching and error - but you cannot be loading and success at the same time.React Query will attempt to compare the old state and the new and keep as much of the previous state as possible to prevent trivial re-rendering
Query Cancellation
Queries that unmount or become unused will be cancelled if you consume the AbortSignal or attach a cancel function to your Promise, the Promise will be cancelled (e.g. aborting the fetch)
import axios from 'axios'
const query = useQuery('todos', ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
})
)
import axios from 'axios'
const query = useQuery('todos', () => {
// Create a new CancelToken source for this request
const CancelToken = axios.CancelToken
const source = CancelToken.source()
const promise = axios.get('/todos', {
// Pass the source token to your request
cancelToken: source.token,
})
// Cancel the request if React Query calls the `promise.cancel` method
promise.cancel = () => {
source.cancel('Query was cancelled by React Query')
}
return promise
})
The query can also be cancelled manually
const [queryKey] = useState('todos')
const query = useQuery(queryKey, async ({ signal }) => {
const resp = await fetch('/todos', { signal })
return resp.json()
})
const queryClient = useQueryClient()
return (
<button onClick={(e) => {
e.preventDefault()
queryClient.cancelQueries(queryKey)
}}>Cancel</button>
)
Setup Provider
To set up the provider for children, so that react-query can be used in component
To customize the default setting for library
import axios from "axios";
import { ReactNode } from "react";
import {
DefaultOptions,
QueryClient,
QueryClientProvider as Provider,
} from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
const defaultOptions: DefaultOptions = {
queries: {
refetchOnReconnect: false,
refetchOnWindowFocus: false,
retry: (failureCount, error) =>
failureCount < 3 &&
!(axios.isAxiosError(error) && error?.response?.status === 401),
},
};
const queryClient = new QueryClient({ defaultOptions });
interface QueryClientProviderProps {
children: ReactNode;
}
export default function QueryClientProvider({
children,
}: QueryClientProviderProps) {
return (
<Provider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</Provider>
);
}
export default function App(){
<QueryClientProvider>
{children}
</QueryClientProvider>
}
UseQuery
Handle fetching data when the page is mounted and the dependencies are changed
Data will be cached based on the query key
interface PageListApiResponse {
results: Page[];
maxPublishedPages: number;
}
export default function usePageListQuery(
state: PageState,
offset: number,
options: UseQueryOptions<PageListApiResponse, AxiosError<any>> = {}
) {
const axios = useContext(AxiosContext);
return useQuery({
queryKey: ["pageList", offset, state],
queryFn: async ({ queryKey }) => {
const { data } = await axios.get<PageListApiResponse>(
`${process.env.REACT_APP_API_URL}/pages?_limit=10&_start=${queryKey[1]}&state=${queryKey[2]}`
);
return data;
},
...options,
});
}
const {
data: { results: listPagesData = [], maxPublishedPages = 0 } = {},
isLoading,
isFetching,
refetch,
} = usePageListQuery(listingStatus, pageOffset, {
onSuccess:(data)=> {...},
onError: (err)=> {...},
enabled: allCountData && allCountData.count > 0,
select: ({ results, ...data }) => ({
...data,
results: [...results].sort((a, b) => {
if (a.url === homePageSettings?.home) return -1;
if (b.url === homePageSettings?.home) return 1;
return 0;
}),
}),
});
UseMutation
Used to update data
export default function usePageUpdateMutation({
onSuccess,
...options
}: MutationOptions<AxiosResponse<Page>, AxiosError<any>, Page> = {}) {
const axios = useContext(AxiosContext);
const queryClient = useQueryClient();
return useMutation({
mutationFn: (page: Page) =>
axios.put<Page>(
`${process.env.REACT_APP_API_URL}/pages/${page.id}`,
page
),
onSuccess: (data, variables, context) => {
if (onSuccess) onSuccess(data, variables, context);
// to clear the query,
// so that the query will be loaded again and trigger isLoading
queryClient.invalidateQueries("allPageList");
queryClient.invalidateQueries(["page", variables.id]);
// set the data to the existing query directly
queryClient.setQueryData(['test', variables.id], data)
},
...options,
});
}
Last updated
Was this helpful?