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>
);
};
