Front. React Query usage

React Query предоставляет удобный способ написания запросов в апи через хуки реакта.

Директории. Наименования. В директории src/api/ создаются директории, название которых должно соответствовать названию домена API. Обычно домен прописывается вначале метода, например, метод GET /catalog/banners:search соответствует домену oms. Наименование файла должно семантически отражать описываемые в нем методы. Например, файл banners.ts содержит методы для CRUD баннера/ов

Структура

api → apiDomainName → method.ts

В директории apiDomainName также есть папка с типами. Типы описываются согласно предоставляемой бекендерами документации в Swagger’е.

 

 

 

 

 

 

 

 

 

 

 

 

Все методы реэкспортируются в файлике index.ts. Для удобства импортов в места использования. Аналогично и с типами.
Например в types/index.ts будет примерно такой код

1 2 export * from './banners.ts'; export * from './brands.ts'

 

Написание хуков

Здесь не должно быть проблем, все дается просто по аналогии с другими примерами.

Не забывать прописывать все типы: для ответа, для ошибки, для параметров (если есть)

1 2 3 4 5 6 7 8 9 10 11 12 import { useMutation, useQuery, useQueryClient } from 'react-query'; import { CommonResponse } from '@api/common/types'; import { client } from '../index'; import { BrandsParams, Brand, BrandBase, BrandBaseWithId, BrandsImageMutateParams } from './types'; const BRANDS_BASE_URL = 'catalog/brands'; export const useBrands = (data: BrandsParams = {}) => useQuery<CommonResponse<Brand[]>, Error>({ queryKey: ['brands', data], queryFn: () => client(`${BRANDS_BASE_URL}:search`, { data }), });

Для хуков, использующих useQuery, не забывать прокидывать параметры в ключ queryKey Иначе, мы не получим прелести автоматического рефетча при изменении параметров.

Также, считаю хорошей практикой, прописав параметр BrandsParams, не разворачивать его так, как это сделано в примере ниже. А делать это по месту использования хука. Потому что разные наборы параметров могут быть необходимы в разных условиях (и судя по swagger'у здесь представлены не все)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 export const useProducts = ({ id, name, code, category, brand, createdAtFrom, createdAtTo, priceFrom, priceTo, page, }: Partial<ProductFilter>, isEnabled = true) => useQuery<ProductProp, Error>({ enabled: isEnabled, queryKey: [ 'products', id, name, code, category, brand, createdAtFrom, createdAtTo, priceFrom, priceTo, page ], queryFn: () => client('catalog/products:search', { data: { filter: { external_id: [code], name, code: id, category_id: category, brand_id: brand, created_at_from: createdAtFrom, created_at_to: createdAtTo, price_from: priceFrom, price_to: priceTo, }, pagination: { limit: ITEMS_PER_PRODUCTS_PAGE, offset: page ? (page - 1)*ITEMS_PER_PRODUCTS_PAGE : 0, type: 'offset', }, }, }), });

 

Пример использования хука мутации

Определим сам хук

1 2 3 4 5 const BASE_URL = 'cms/banners'; export const useCreateProductGroupBanner = () => useMutation<BannerCreateResponse, Error, BannerCreateParams>( data => client(`${BASE_URL}`, { data }));

Добавим хук на страницу.

1 const createProductGroupBanner = useCreateProductGroupBanner();

И используем метод mutateAsync

1 2 3 const res = await createProductGroupBanner.mutateAsync(data)); // какой-то код с res setBanner(res.data);

Обратите внимание, константа createProductGroupBanner – это объект. Но здесь мы не будем использовать деструктуризацию, для того, чтобы не заниматься бесконечным переименованием в случае использования других хуков мутации на одной странице. Например

1 2 3 4 5 6 7 8 9 10 11 const { data: apiRegions, isLoading: isLoadingRegions, error: regionsError, } = useRegions(); const { data: apiPrices, isLoading: isPricesLoading, error: pricesError, } = useDeliveryPrices();

 

Инвалидация кеша.

После использования хука мутации, а именно удаления/обновления/добавления какой-нибудь сущности, то нужно инвалидировать кеш и переполучить данные либо для конкретной сущности, либо для всего списка сущностей. Можно сделать оптимистичное обновление интерфейса и в случае неудачи откатиться назад, но это немного сложно, и на мой взгляд, для этого проекта избыточно.

Сейчас на проекте встречается 2 способа инвалидации кэша:

Первый. После успешной мутации мы скидываем кеш для конкретных ключей. В примере ниже, кэш станет невалидным для всех ключей, начинающихся с 'brands'. Это действие произойдет автоматически.

1 2 3 4 5 6 7 export const useCreateBrand = () => { const queryClient = useQueryClient(); return useMutation<CommonResponse<Brand>, Error, BrandBase>(data => client(BRANDS_BASE_URL, { data }), { onSuccess: () => queryClient.invalidateQueries('brands'), }); };

Второй. Мы вручную переполучим данные. Для этого нам нужно получить метод refetch из хука, который использует под капотом useQuery. А потом этот refetch используем в нужном месте, например, при успехе обновления данных об одном конкретном пользователе

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const { data, refetch } = useCustomer(id); const customer = data?.data; const updateCustomer = useUpdateCustomer(); const onCustomerUpdate = async (values: FormikValues) => { if (customer) { updateCustomer.mutate( { ...customer, birthday: values?.birthday?.toString(), gender: +values?.gender, city: values?.city, comment_internal: values?.comment, }, { onSuccess: () => { refetch(); }, } ); } }

Какой из двух способов использовать, решать вам. Но помните, что в первом случае инвалидация кеша и приведет к переполучению всех данных, чей ключ прокинут в invalidateQuery(key). И это будет происходить всякий раз при мутации.