Как грамотно обрабатывать isFetching для одного и того же запроса в нескольких компонентах (React Query)?

Ссылка скопирована
1 ответ

Есть запрос:

export const useGetClients = (params?: GetClientsRequest) => useQuery({ queryKey: ['clients', 'list', params], queryFn: () => ClientClient.getClientApiInstance().getClients(params), });

По вводным: на странице два основных компонента: таблица и кнопка, открывающая сайдбар.

Таблица:

const Wallets = () => { const { wallets, isLoading, isFetching } = useGetWallets(); return ( <div className="flex flex-col gap-4"> <div className="flex flex-wrap items-center justify-between gap-2"> <DepositFundsButton /> </div> <DataTable columns={Columns} data={wallets} isLoading={isLoading} isFetching={isFetching} /> </div> ); };

Где:

export const useGetWallets = () => { const { data: accounts, isLoading: isAccountsLoading, isFetching: isAccountsFetching, } = useGetLedgerAccounts(); const { data: clients, isLoading: isClientsLoading, isFetching: isClientsFetching, } = useGetClients({ clientType: ClientType.Client, }); const accountsWithClientName: AccountWithClientName[] = accounts && clients ? accounts.map((account) => ({ ...account, context: { ...account.context, ...(account.context.clientId && { clientName: clients.clients.find( (client) => client.id === account.context.clientId, )?.name, }), }, })) : []; return { wallets: accountsWithClientName, isLoading: isAccountsLoading || isClientsLoading, isFetching: isAccountsFetching || isClientsFetching, }; };

По вводным: при нажатии на кнопку Deposit funds открывается сайдбар с формой.
По вводным: в форме тот же запрос вызывается повторно (с теми же параметрами), чтобы получить список клиентов для выпадающего списка:

export const DepositFundsForm = ({ onClose }: DepositFundsFormProps) => { const { data, isFetching: isClientsFetching } = useGetClients({ clientType: ClientType.Client, }); return ( <> <Form {...methods}> <form className="space-y-6 overflow-y-auto px-4"> <SelectField name="clientId" loading={isClientsFetching} control={control} label="Client" placeholder="Client" options={clientOptions} className="min-w-[300px]" /> </form> </Form> <SheetFooter> <SheetClose asChild> <Button variant="secondary">Cancel</Button> </SheetClose> <Button onClick={handleSubmit(onSubmit)} isLoading={isSubmitting}> Deposit </Button> </SheetFooter> </> ); };

Проблема:
По вводным: когда форма открыта, я вижу два спиннера — один в таблице и второй в сайдбаре. С UX точки зрения это выглядит неправильно, ведь запрос одинаковый и данные уже есть в кеше.

Возможные решения:
Сейчас ситуация такая: - Показывать спиннер в таблице только если isAccountsFetching, а не при isAccountsFetching || isClientsFetching.
По вводным: - Передавать дополнительный ключ запроса (query key) из таблицы или сайдбара, чтобы у них были разные ключи и независимые состояния.
По вводным: - Обернуть таблицу и кнопку с сайдбаром в контекст-провайдер, где данные клиентов будут загружаться один раз и шариться между компонентами.
По вводным: в случае с последним решением возникают вопросы:
Нужно понять: a) что показывать, пока в провайдере идёт загрузка клиентов — скелетон вместо таблицы?
По вводным: b) если форма будет использоваться в других местах, нужно всегда оборачивать её в провайдер, что может быть неудобно

Вопрос:
Нужно понять: какой подход здесь будет наиболее правильным с точки зрения UX и архитектуры кода?

Нужно решить такую задачу?

Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

Заказать помощь
Лучший ответ
1
Ольга Сеть Ответ

В React Query isFetching привязан к конкретному query key. Если таблица и кнопка сайдбара используют один и тот же хук с одинаковым queryKey, оба компонента будут видеть фоновые refetch-состояния одного запроса. Это нормально: React Query показывает состояние запроса, а не конкретного компонента.

Сначала убедитесь, что ключ стабилен. Если params каждый рендер создаётся новым объектом, запрос может вести себя шумно:

const params = useMemo(() =&gt; ({ page, filters }), [page, filters]);
const query = useGetClients(params);

const params = useMemo(() =&gt; ({ page, filters }), [page, filters]); const query = useGetClients(params);

Если таблице нужен loader при первом получении, используйте isLoading. Если нужно показать маленький индикатор обновления — isFetching. Для кнопки обычно не надо реагировать на общий isFetching, если кнопка просто открывает сайдбар.

Можно разделить UI-состояния:

const { data, isLoading, isFetching } = useGetClients(params);
 
return (
  &lt;&gt;
    {isLoading ? &lt;Skeleton /&gt; : &lt;Table data={data} dimmed={isFetching} /&gt;}
    &lt;Button disabled={false}&gt;Открыть сайдбар&lt;/Button&gt;
  &lt;/&gt;
);

const { data, isLoading, isFetching } = useGetClients(params); return ( &lt;&gt; {isLoading ? &lt;Skeleton /&gt; : &lt;Table data={data} dimmed={isFetching} /&gt;} &lt;Button disabled={false}&gt;Открыть сайдбар&lt;/Button&gt; &lt;/&gt; );

Если нужно узнать, идёт ли запрос в любом месте приложения, используйте useIsFetching с фильтром:

const clientsFetching = useIsFetching({
  queryKey: ['clients', 'list']
});

const clientsFetching = useIsFetching({ queryKey: ['clients', 'list'] });

Но не блокируйте unrelated UI без необходимости. Фоновый refetch не должен ломать работу кнопок.

Итог: isFetching общий для одинакового query key. Для таблицы используйте его как индикатор обновления, для первичной загрузки — isLoading, а кнопки не завязывайте на isFetching, если они не зависят от свежести данных.

Другие ответы (0)

Пока нет других ответов. Будьте первым, кто поможет автору.

Ответить на вопрос

комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может быть интересно