React Query
What is React Query?
React Query is a powerful library for managing server-side state in React applications. It simplifies data fetching, caching, synchronization, and updating data from APIs by providing declarative hooks. React Query also handles complex logic like caching, background refetching, retries, and more, making data management easier and improving the performance of React apps.
How do you install and set up React Query?
You can install React Query using npm or yarn:
npm install @tanstack/react-query
yarn add @tanstack/react-query
After installing, you need to wrap your application with the QueryClientProvider
and create a QueryClient
to manage queries.
Example of setting up React Query:
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
// Create a QueryClient
const queryClient = new QueryClient();
function Root() {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
}
export default Root;
In this example, the entire app is wrapped with the QueryClientProvider
to give access to React Query's functionality.
How do you fetch data using useQuery
in React Query?
To fetch data with React Query, you use the useQuery
hook. This hook takes a unique key and a function that fetches the data. React Query automatically handles caching, refetching, and error states.
Example of fetching data using useQuery
:
import { useQuery } from '@tanstack/react-query';
function fetchData() {
return fetch('https://api.example.com/data').then((response) => response.json());
}
function DataComponent() {
const { data, error, isLoading } = useQuery(['data'], fetchData);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
In this example, useQuery
fetches data from the API, handles loading and error states, and returns the data.
What is the purpose of the unique query key in useQuery
?
The unique query key is a string or an array that React Query uses to identify and cache the data. Every query must have a unique key to ensure that React Query can cache, update, and invalidate the data separately. The key also helps React Query to refetch data when it becomes stale.
Example of a query key:
useQuery(['data', { id: 1 }], fetchData);
In this example, ['data', { id: 1 }]
is used as the unique query key for fetching and caching data related to the specific id
.
How do you handle loading and error states with React Query?
React Query automatically manages loading and error states, which are returned by the useQuery
hook. The isLoading
property indicates whether the data is being fetched, and the error
property contains any errors encountered during the fetch.
Example of handling loading and error states:
const { data, isLoading, error } = useQuery(['data'], fetchData);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
In this example, the component renders a loading message while the data is being fetched and an error message if the request fails.
How do you refetch data in React Query?
React Query provides multiple ways to refetch data. By default, data is refetched in the background when it becomes stale, but you can manually trigger refetching using the refetch
function provided by useQuery
.
Example of manually refetching data:
const { data, refetch, isFetching } = useQuery(['data'], fetchData);
return (
<div>
<button onClick={() => refetch()}>Refetch Data</button>
{isFetching && <p>Fetching...</p>}
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
In this example, clicking the button triggers the refetch
function to fetch the latest data from the API.
How do you cache and invalidate queries in React Query?
React Query automatically caches fetched data and provides tools to invalidate and refetch queries when the data becomes stale. You can invalidate queries using the invalidateQueries
function, which forces a query to refetch and update its data.
Example of invalidating a query:
import { useQueryClient } from '@tanstack/react-query';
function InvalidateQueryComponent() {
const queryClient = useQueryClient();
const invalidateData = () => {
queryClient.invalidateQueries(['data']); // Invalidate and refetch the 'data' query
};
return <button onClick={invalidateData}>Invalidate Data</button>;
}
In this example, calling invalidateQueries
forces React Query to refetch the data for the ['data']
query key.
What is the difference between isFetching
and isLoading
in React Query?
isLoading
is true
only during the initial data fetch (when there is no cached data), while isFetching
is true
whenever the query is fetching new data, even if cached data is available. This means isFetching
is useful for showing background fetching indicators.
Example:
const { data, isLoading, isFetching } = useQuery(['data'], fetchData);
if (isLoading) return <p>Loading...</p>;
if (isFetching) return <p>Updating...</p>;
return <p>Data Loaded</p>;
In this example, isLoading
shows the loading state for the initial fetch, and isFetching
shows when data is being refetched in the background.
How do you use useMutation
in React Query?
The useMutation
hook is used to handle data-modifying operations like creating, updating, or deleting data. Unlike useQuery
, which is used for fetching data, useMutation
is used for non-idempotent operations (e.g., submitting forms).
Example of using useMutation
:
import { useMutation } from '@tanstack/react-query';
function submitData(newData) {
return fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(newData),
headers: {
'Content-Type': 'application/json',
},
});
}
function SubmitForm() {
const mutation = useMutation(submitData);
const handleSubmit = (event) => {
event.preventDefault();
const formData = { name: event.target.name.value };
mutation.mutate(formData);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />
<button type="submit">Submit</button>
{mutation.isLoading && <p>Submitting...</p>}
{mutation.isError && <p>Error submitting data</p>}
</form>
);
}
In this example, useMutation
handles form submission by calling the submitData
function and managing loading and error states.
How do you invalidate queries after a mutation in React Query?
After performing a mutation (e.g., adding or updating data), you may need to invalidate queries to ensure that the cached data is up-to-date. This can be done using useMutation
along with the onSuccess
callback to invalidate related queries.
Example of invalidating queries after a mutation:
import { useMutation, useQueryClient } from '@tanstack/react-query';
function SubmitForm() {
const queryClient = useQueryClient();
const mutation = useMutation(submitData, {
onSuccess: () => {
queryClient.invalidateQueries(['data']); // Invalidate the 'data' query after mutation
},
});
const handleSubmit = (event) => {
event.preventDefault();
const formData = { name: event.target.name.value };
mutation.mutate(formData);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
);
}
In this example, after successfully submitting the form, the ['data']
query is invalidated to refetch the updated data.
What are some advantages of using React Query?
Some key advantages of using React Query include:
- Automatic caching: React Query automatically caches data and retrieves it from the cache when possible, reducing unnecessary network requests.
- Background refetching: React Query can refetch data in the background to keep the UI up-to-date without disrupting the user experience.
- Optimistic updates: React Query supports optimistic updates, allowing the UI to update immediately while waiting for the server response.
- Automatic retries: Queries can automatically retry failed requests, improving resilience against temporary network failures.
- Declarative data fetching: React Query provides a declarative way to handle data fetching, loading, and error states, reducing boilerplate code.