В статье будут рассмотрены:
- случаи использования React Query;
- простые запросы
fetch
с помощью пакета React Query; - поиск элементов через API по ID;
- пагинация;
- мутации.
Случаи использования React Query
Традиционный метод fetch()
отлично подходит для извлечения данных с помощью API. Однако по мере разрастания и усложнения приложения вы можете столкнуться с рядом трудностей. Например:
- Кэширование. Для сохранения в кэше ответов на запросы разработчик должен разобраться с заголовками кэширования и браузерным кэшированием. В этом заключается главная сложность. В дальнейшем вам также придется уведомить React о моменте повторного извлечения данных, т. е. сообщить ему о том, что данные устарели и требуют обновления.
- Пагинация. А что если нужно отобразить огромные объемы данных для пользователя? В этом случае необходимо реализовать поддержку пагинации. Однако даже если это возможно, добавление такой функциональность обернется еще одной проблемой.
И вот тут в дело вступает пакет React Query. Эта библиотека берет заботы о кэшировании на себя, вследствие чего пропадает необходимость работать с заголовками кэширования и браузерным кэшированием, а также упрощается процесс пагинации.
Итак, React Query облегчает нам жизнь, устраняя многие проблемы, связанные с получением данных и управлением состоянием сервера.
Теперь же, обсудив ее преимущества, приступаем к написанию кода!
React Query: основы
В данном разделе статьи будут рассмотрены:
- простые вызовы API;
- поиск по ID.
Настройка проекта
Сначала необходимо инициализировать репозиторий. Для этого выполняем следующую команду в терминале:

В данном проекте применяются такие сторонние библиотеки, как:
- React-query — для выполнения запросов
fetch
иpost
к API. - Formik — для создания текстовой формы, позволяющей пользователю осуществлять поиск данных по ID.
Для их установки вводим в терминал команду:

После этого переходим в src/App.js
и удаляем код между тегами div
. В итоге файл src/App.js
должен выглядеть таким образом:

Получаем следующий результат:

Теперь переходим к выполнению простых запросов fetch
с помощью React Query.
Получение и отображение данных
На первом этапе создаем файл Passenger.js
в директории src
. Он отвечает за получение данных с сервера. В статье используется Fake REST API.
В src/Passenger.js
прописываем код:
import { useQuery } from "react-query";
function Passengers() {
const { isLoading, error, data, isSuccess } = useQuery("passengers", () =>
fetch(
"https://api.instantwebtools.net/v1/passenger?page=0&size=10"
).then((res) => res.json())
);
return (
<div>
{isSuccess &&
data.data.map((item) => (
<div key={item._id}>
<p>{item.name}</p>
<p>{item._id}</p>
</div>
))}
{isLoading && <p>Loading..</p>}
{error && <p>Error occurred!</p>}
</div>
);
}
export default Passengers;
- Строка 1. Импортируем метод
useQuery
из пакета React Query, позволяющий выполнять запросыfetch
. - Строка 4. Извлекаем поля
isLoading
,error
,data
иisSuccess
из хукаuseQuery
. Первый параметрuseQuery
— это ключ, применяемый для идентификации запроса. - Строки 5-7. Сообщаем React о намерении выполнить запрос
fetch
к API, после чего преобразуем полученные данные в JSON. - Строка 11. Если запрос был успешным (
isSuccess
являетсяtrue
), то отображаем данные. В нашем случае ими будут поляid
иname
каждого элемента. - Строки 12–13. Если запрос продолжает загружаться или вернул ошибку (
isLoading
являетсяtrue
илиerror
не являетсяnull
), то показываем надлежащее сообщение.
Далее переходим в App.js
и импортируем QueryClient
, QueryClientProvider
из пакета React Query , а также компонент Passengers
:

Непосредственно поверх объявления компонента App
пишем следующий фрагмент кода:

- Строка 1. Создаем экземпляр
QueryClient
для взаимодействия с кэшем.
Теперь находим данный фрагмент кода в App.js
:

Заменяем его блоком кода, указанным ниже:

- Строка 3.
QueryClientProvider
служит мостом между приложением иQueryClient
, иначе говоря, позволяет реализовать кэширование в приложении. - Строка 4. Отрисовываем компонент
Passengers
.
Выполняем код, получая следующий результат:

Как видим, код работает. Простой запрос fetch
успешно выполнен с помощью React Query.
В следующем разделе узнаем, как осуществлять поиск конкретных данных посредством ID.
В итоге App.js
должен выглядеть так:
import { QueryClient, QueryClientProvider } from "react-query";
import Passengers from "./Passengers";
const queryClient = new QueryClient();
function App() {
return (
<div>
<QueryClientProvider client={queryClient}>
<Passengers />
</QueryClientProvider>
</div>
);
}
export default App;
Поиск по ID
В каталоге src
создаем файл PassengerID.js
. Компонент PassengerID
позволит пользователю искать данные пассажира всего лишь путем ввода ID.
В src/PassengerID.js
начинаем с импорта библиотек:

- Строка 1. Используем переменную состояния для отслеживания ID.
- Строка 2 помогает в выполнении запросов к API.
- Хук
useFormik
содействует в создании форм.
Далее пишем код в файле PassengerID
:
function PassengerID() {
const [id, setID] = useState("");
const formik = useFormik({
initialValues: {
_id: "",
},
onSubmit: (values) => {
console.log(JSON.stringify(values, null, 2));
setID(values._id);
},
});
const fetchPassenger = async (id) => {
const res = await fetch(
`https://api.instantwebtools.net/v1/passenger/${id}`
);
return res.json();
};
const { data, error, isLoading } = useQuery(["passengerID", id], () =>
fetchPassenger(id)
);
}
export default PassengerID;
- Строка 2. Хук
id
сообщает React Query идентификатор, по которому нужно произвести запрос через API. - Строка 3. Хук
useFormik
помогает в создании формы. Здесь мы информируем Formik о том, что начальное значение текстового поля_id
будет пустым. - Строка 7. Если пользователь отправляет форму, то вызываем функцию
setID
для изменения переменнойid
на значение, введенное им в текстовое поле. - Строка 12. Объявляем функцию
fetchPassenger
, которая выберет пользователя в соответствии с ID, присутствующим в параметре. В итоге сырые данные будут преобразованы в JSON и затем возвращены. - Строка 18. Запускаем функцию
useQuery
для выполнения запросаfetch
к API. Обратите внимание на добавление в ключ переменной состоянияid
. Дело в том, что наш запрос зависит от переменнойid
. Таким образом мы даем указание React выполнять запрос при каждом изменении состоянияid
. - Строка 19. Вызываем функцию
fetchPassenger
и передаем параметрid
.
Данные извлечены, осталось их только отобразить.
В довершении всего добавляем код в PassengerID.js
:
return (
<div>
<h1>Find by ID</h1>
<form onSubmit={formik.handleSubmit}>
<input
id="_id"
name="_id"
type="text"
onChange={formik.handleChange}></input>
</form>
{error && <p>Error!</p>}
{data && (
<p>
{data.name}, {data.trips}
</p>
)}
{isLoading && <p>Loading..</p>}
</div>
);
- Строка 4. Создаем элемент
form
и в случае отправки пользователем формы инструктируем React запустить соответствующий обработчик. - Строка 5. Создаем текстовое поле
input
сid=”_id"
иname="_id"
. Необходимо отметить, что мы передаем это полеid
иname
, соответствующее свойству, которое было определено ранее в свойствеinitialValues
, расположенном в хукеuseFormik
. - Строка 12. Если данные возвращаются, то отображаем поля
name
иtrips
(поездок) пассажира.
Теперь код принял свой окончательный вид. На этом этапе необходимо отрисовать компонент PassengerID
в DOM.
В App.js
находим фрагмент кода:

Меняем его следующим образом:

- Строка 5. Отрисовываем компонент
PassengerID
.
Выполняем код, в результате получая:

Убеждаемся в работоспособности кода. Обратите внимание, что при каждом изменении переменной состояния id
, происходит повторное извлечение данных.
Тема данного раздела исчерпана, и пора переходить к следующему, в котором подробно будем знакомиться с библиотекой React Query.
Напоследок покажем, как должен выглядеть PassengerID.js
:
import { useState } from "react";
import { useQuery } from "react-query";
import { useFormik } from "formik";
import Passengers from "./Passengers";
function PassengerID() {
const [id, setID] = useState("");
const formik = useFormik({
initialValues: {
_id: "",
},
onSubmit: (values) => {
console.log(JSON.stringify(values, null, 2));
setID(values._id);
},
});
const fetchPassenger = async (id) => {
const res = await fetch(
`https://api.instantwebtools.net/v1/passenger/${id}`
);
return res.json();
};
const { data, error, isLoading } = useQuery(["passengerID", id], () =>
fetchPassenger(id)
);
return (
<div>
<h1>Find by ID</h1>
<form onSubmit={formik.handleSubmit}>
<input
id="_id"
name="_id"
type="text"
onChange={formik.handleChange}
></input>
</form>
{error && <p>Error!</p>}
{data && (
<p>
{data.name}, {data.trips}
</p>
)}
{isLoading && <p>Loading..</p>}
</div>
);
}
export default PassengerID;
React Query: дополнительные возможности
В этом разделе рассмотрим:
- пагинацию;
- мутации.
Начнем с пагинации.
Пагинация
Fake API содержит тысячи записей. Несмотря на то, что можно отобразить их все на одной странице, лучше попрактиковаться в разбивке этого списка по нескольким. Например, поместить первые десять на одну страницу, а следующие десять — на вторую и т. д.
В файле Passengers.js
объявляем переменную состояния page
:

Это позволит отслеживать текущую страницу.
Далее пишем такой фрагмент кода:

В этой функции мы инструктируем React извлечь записи через API с указанной страницы. В итоге возвращаются преобразованные данные JSON.
Далее находим следующий фрагмент кода:

Теперь меняем его таким образом:

- Строка 2. Сообщаем React, что запрос зависит от переменной
page
. При изменении состоянияpage
выполняем запрос заново. - Строка 3. Извлекаем данные пассажира с помощью переменной
page
в качестве параметра. - Строка 4.
keepPreviousData
дает указание React сохранять старые данные при изменении ключа запроса.
Мы почти у цели. Далее находим блок return
в Passengers.js
:

Добавляем следующие строки кода сразу после открывающего тега div
:

- Строка 3. Инструктируем React уменьшить состояние
page
и остановить его при достижении 0. - Строка 4. Увеличиваем состояние
page
. - Строка 5. Отображаем значение
page
.
Вот теперь закончили! Выполняем код, в результате получая:

Как видим, код работает. Теперь займемся мутациями.
Окончательный вариант кода Passengers.js
:
import { useState } from "react";
import { useQuery } from "react-query";
function Passengers() {
const [page, setPage] = useState(0);
const fetchPassengers = async (page) => {
const res = await fetch(
`https://api.instantwebtools.net/v1/passenger?page=${page}&size=10`
);
return res.json();
};
const { isLoading, error, data, isSuccess } = useQuery(
["passengers", page],
() => fetchPassengers(page),
{ keepPreviousData: false }
);
return (
<div>
<button onClick={() => setPage((old) => Math.max(0, old - 1))}>
{" "}
-{" "}
</button>
<button onClick={() => setPage((old) => old + 1)}> + </button>
<p> {page} </p>
{isSuccess &&
data.data.map((item) => (
<div key={item._id}>
<p>{item.name}</p>
<p>{item._id}</p>
</div>
))}
{isLoading && <p>Loading..</p>}
{error && <p>Error occurred!</p>}
</div>
);
}
export default Passengers;
Мутации
React Query — отличная библиотека для выполнения запросов GET
. Выясним, как изменять или добавлять данные на сервер.
Здесь в дело вступают мутации, позволяющие выполнять запросы POST
и PUT
.
Но прежде установим axios для осуществления вышеуказанных запросов к API.

В директории src
создаем файл AddPassenger.js
. Начинаем с импорта модулей:

- Строка 1. Объявляем переменные состояния для последующей их отправки к API в качестве данных.
- Строка 2. Хук
useMutation
инструктирует React изменить данные на сервере. - Строка 3. Axios разрешает выполнить запросы
POST
к API. - Строка 4 способствует извлечению данных из формы.
function AddPassenger() {
const formik = useFormik({
initialValues: {
name: "",
trips: 0,
airline: 1,
},
onSubmit: (values) => {
console.log(JSON.stringify(values, null, 2));
mutation.mutate({
name: values.name,
trips: values.trips,
airline: values.airline,
});
},
});
}
export default AddPassenger;
- Строки 2–6. Создаем хук
useFormik
, имеющий 3 значенияname
,trips
иairline
. - Строки 8–16. При отправке пользователем формы выполняем мутацию. Отправляем значения текстовых полей в качестве данных на сервер. В дальнейшем определяем
mutation
.
Затем пишем фрагмент код:
const mutation = useMutation((item) =>
axios.post("https://api.instantwebtools.net/v1/passenger/", item)
);
if (mutation.isSuccess) console.log(mutation.data.data);
- Строка 1. Создаем экземпляр
useMutation
. - Строка 2. Выполняем запрос
POST
к API. Переменнаяitem
является телом данных, которые будут отправлены позже. - Строка 4. Если мутация была успешной (свойство
isSuccess
являетсяtrue
), то регистрируем возвращаемые данные в консоли.
С логикой закончили, осталось отобразить форму, обеспечивая пользователю возможность ввода данных.
Пишем следующий код в файле AddPassenger.js
:
return (
<div>
<h1>Submit form</h1>
<form onSubmit={formik.handleSubmit}>
<label>
Name
<input id="name" type="text" onChange={formik.handleChange} />
</label>
<label>
Trips
<input id="trips" type="number" onChange={formik.handleChange} />
</label>
<label>
Airline:
<input id="airline" type="number" onChange={formik.handleChange} />
</label>
<button type="submit">Submit</button>
</form>
{mutation.isLoading && <p>Please wait</p>}
{mutation.isSuccess && <p>Success! ID: {mutation.data.data._id}</p>}
</div>
);
- Строка 4. Запускаем обработчик отправки Formik, когда пользователь отправляет форму.
- Строки 7–15. Создаем несколько текстовых полей для
name
,trips
иairline
. - Строка 19. Если запрос загружается (свойство
isLoading
являетсяtrue
), то отображаем надлежащее сообщение. - Строка 20. В случае успешного выполнения мутации (свойство
isSuccess
являетсяtrue
) отображаем ID нового пассажира.
Работа над этим кодом завершена, осталось отобразить компонент AddPassenger
в DOM.
Переходим к src/App.js
и находим блок кода:

Меняем его таким образом:

- Строка 4. Отображаем компонент
AddPassenger
.
Выполняем код и получаем результат:

Код работает. Мы получили ID пассажира. Попробуем его найти.

Все прекрасно функционирует!
Дополнительные источники
Репозиторий GitHub
Заключение
Вот такая неимоверно легкая в применении React Query. Нам не пришлось писать много кода для добавления поддержки пагинации или выполнения запроса POST
. Неудивительно, что крупные корпорации, например Microsoft и eBay, используют для продакшена именно React Query.
Если по ходу изучения статьи что-то осталось непонятным, советуем детально проанализировать код и поэкспериментировать с ним, чтобы полностью разобраться во внутренней работе библиотеки.
Благодарим за внимание!
Читайте также:
- 5 Методов сохранения состояния в промежутках между перезагрузками страниц в React
- Создаем приложение React с нуля в 2021 году
- Обзор техник кэширования в React
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Hussain Arif: How To Make Better Queries With React Query