React Query - por que isso importa?

1 de Setembro, 20252 min de leitura
React Query - por que isso importa?
#react#javascript#webdev

Evita o inferno de useEffect e gerencia: estado de requisições, cache, refetch, retry, "suspending" e tratamento de erros; tudo pronto para uso.

Ajuda com o gerenciamento de Estado Assíncrono.

Inferno de useEffect

Você provavelmente não precisa de useEffect, especialmente para lidar com requisições.

Diferença de código

ruim

import { useState, useEffect } from 'react';

const UniverseList = () => {
  const [isLoading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [universes, setUniverses] = useState([]);

  useEffect(() => {
    const controller = new AbortController();

    const loadUniverses = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch('/api/universes', {
          signal: controller.signal
        });

        if (!response.ok) {
          throw new Error(`Error: ${response.status} - ${await response.text()}`);
        }

        const jsonResponse = await response.json();
        setUniverses(jsonResponse.data || []);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message || 'Failed to load universes');
        }
      } finally {
        setLoading(false);
      }
    };

    loadUniverses();

    return () => {
      controller.abort();
    };
  }, []);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {universes.map(universe => (
        <div key={universe.id}>{universe.name}</div>
      ))}
    </div>
  );
};

export default UniverseList;

bom

import { Suspense } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';

const FIVE_MINUTES = 5 * 60 * 1_000;
const TEN_MINUTES  = 10 * 60 * 1_000;

const fetchUniverses = async () => {
  const response = await fetch('/api/universes');

  if (!response.ok) {
    throw new Error(`Error: ${response.status} - ${await response.text()}`);
  }

  const jsonResponse = await response.json();
  return jsonResponse.data || [];
};

const UniverseListContent = () => {
  const { data: universes } = useSuspenseQuery({
    queryKey: ['universes'],
    queryFn: fetchUniverses,
    staleTime: FIVE_MINUTES,
    gcTime: TEN_MINUTES,
    retry: 3,
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
  });

  return (
    <div>
      {universes.map(universe => (
        <div key={universe.id}>{universe.name}</div>
      ))}
    </div>
  );
};

const ErrorFallback = ({ error, resetErrorBoundary }) => (
  <div role="alert">
    <p>Something went wrong:</p>
    <pre>{error.message}</pre>
    <button onClick={resetErrorBoundary}>Try again</button>
  </div>
);

const UniverseListWithSuspense = () => {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => window.location.reload()}
    >
      <Suspense fallback={<div>Loading universes...</div>}>
        <UniverseListContent />
      </Suspense>
    </ErrorBoundary>
  );
};

export default UniverseListWithSuspense;

Por que ruim?

Código repetido, necessário para gerenciar estado assíncrono, se espalhará como ervas daninhas à medida que o projeto escala.

Se você não está disposto a usar React Query, pelo menos crie seus próprios hooks desacoplados e certifique-se de testá-los adequadamente.

cache

React Query pode cachear os resultados dos seus endpoints e expirá-los após um tempo de expiração configurável.

Também permite que você vincule tags com queries e as invalide em mutations.

refetch

Tão fácil quanto chamar uma função, como deveria ser.

const UniverseListContent = () => {
  const { data: universes, refetch } = useSuspenseQuery({
    queryKey: ['universes'],
    queryFn: fetchUniverses,
    staleTime: FIVE_MINUTES,
    gcTime: TEN_MINUTES,
    retry:
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
  });

  return (
    <div>
	  <button onClick={refetch}>Reload All</button>
      {universes.map(universe => (
        <div key={universe.id}>{universe.name}</div>
      ))}
    </div>
  );
};