Найти тему
Nuances of programming

React Query - залог эффективных запросов

Оглавление

Источник: Nuances of Programming

В статье будут рассмотрены:

  • случаи использования 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 должен выглядеть таким образом:

В App.js код должен выглядеть именно так
В 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.js
Код для App.js

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

Код для App.js
Код для App.js
  • Строка 1. Создаем экземпляр QueryClient для взаимодействия с кэшем.

Теперь находим данный фрагмент кода в App.js:

Код, который нужно найти в App.js
Код, который нужно найти в 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 начинаем с импорта библиотек:

Код для PassengerID
Код для PassengerID
  • Строка 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 находим фрагмент кода:

Код, который нужно найти в App.js
Код, который нужно найти в App.js

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

Код для App.js
Код для 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:

Код для Passengers.js
Код для Passengers.js

Это позволит отслеживать текущую страницу.

Далее пишем такой фрагмент кода:

Код для Passengers.js
Код для Passengers.js

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

Далее находим следующий фрагмент кода:

Код, который нужно найти в Passengers.js
Код, который нужно найти в Passengers.js

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

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

Мы почти у цели. Далее находим блок return в Passengers.js:

Код, который нужно найти в Passengers.js
Код, который нужно найти в Passengers.js

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

Код для Passengers.js
Код для Passengers.js
  • Строка 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.

Ввод команды в терминал для установки axios
Ввод команды в терминал для установки axios

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

Код для AddPassenger.js
Код для 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 и находим блок кода:

Код, который нужно найти в App.js
Код, который нужно найти в App.js

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

Замена кода в App.js
Замена кода в App.js
  • Строка 4. Отображаем компонент AddPassenger.

Выполняем код и получаем результат:

Вывод кода
Вывод кода

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

Поиск по ID
Поиск по ID

Все прекрасно функционирует!

Дополнительные источники

Репозиторий GitHub

Заключение

Вот такая неимоверно легкая в применении React Query. Нам не пришлось писать много кода для добавления поддержки пагинации или выполнения запроса POST. Неудивительно, что крупные корпорации, например Microsoft и eBay, используют для продакшена именно React Query.

Если по ходу изучения статьи что-то осталось непонятным, советуем детально проанализировать код и поэкспериментировать с ним, чтобы полностью разобраться во внутренней работе библиотеки.

Благодарим за внимание!

Читайте также:

Читайте нас в Telegram, VK

Перевод статьи Hussain Arif: How To Make Better Queries With React Query