W kosmicznej podróży przez React dotarliśmy do punktu, gdzie zarządzanie danymi serwerowymi staje się kluczowe. React Query (obecnie TanStack Query) to rewolucyjna biblioteka, która zmienia sposób, w jaki myślimy o fetching danych, cachingu i synchronizacji stanu serwerowego z UI. Podobnie jak zaawansowany system nawigacji automatycznie śledzi pozycję statku kosmicznego, React Query automatycznie zarządza stanem twoich danych.
Tradycyjne podejście z useEffect i fetch ma wiele problemów:
1// Tradycyjne podejście - WIELE PROBLEMÓW
2function UserProfile({ userId }) {
3 const [user, setUser] = useState(null);
4 const [loading, setLoading] = useState(true);
5 const [error, setError] = useState(null);
6
7 useEffect(() => {
8 setLoading(true);
9 fetch(`/api/users/${userId}`)
10 .then(res => res.json())
11 .then(data => {
12 setUser(data);
13 setLoading(false);
14 })
15 .catch(err => {
16 setError(err);
17 setLoading(false);
18 });
19 }, [userId]);
20
21 // Problemy:
22 // 1. Brak cachingu - te same dane pobierane wielokrotnie
23 // 2. Brak automatycznego refetching
24 // 3. Brak optymistycznych aktualizacji
25 // 4. Brak deduplication requests
26 // 5. Brak obsługi stale data
27 // 6. Trzeba ręcznie zarządzać loading/error states
28 // 7. Problemy z race conditions
29
30 if (loading) return <div>Ładowanie...</div>;
31 if (error) return <div>Błąd: {error.message}</div>;
32 return <div>{user.name}</div>;
33}React Query rozwiązuje te wszystkie problemy:
1// React Query - ELEGANCKIE I POTĘŻNE
2import { useQuery } from '@tanstack/react-query';
3
4function UserProfile({ userId }) {
5 const { data: user, isLoading, error } = useQuery({
6 queryKey: ['user', userId],
7 queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json())
8 });
9
10 if (isLoading) return <div>Ładowanie...</div>;
11 if (error) return <div>Błąd: {error.message}</div>;
12 return <div>{user.name}</div>;
13}1# Instalacja TanStack Query (React Query v5)
2npm install @tanstack/react-query
3npm install @tanstack/react-query-devtools # Opcjonalne DevTools1// App.jsx - Setup QueryClient
2import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
4
5// Stwórz Query Client z konfiguracją
6const queryClient = new QueryClient({
7 defaultOptions: {
8 queries: {
9 staleTime: 1000 * 60 * 5, // 5 minut
10 cacheTime: 1000 * 60 * 30, // 30 minut
11 refetchOnWindowFocus: true,
12 refetchOnReconnect: true,
13 retry: 3,
14 retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
15 },
16 },
17});
18
19function App() {
20 return (
21 <QueryClientProvider client={queryClient}>
22 <YourApp />
23 {/* DevTools - widoczne tylko w development */}
24 <ReactQueryDevtools initialIsOpen={false} />
25 </QueryClientProvider>
26 );
27}1import { useMutation, useQueryClient } from '@tanstack/react-query';
2
3async function createMission(newMission) {
4 const response = await fetch('/api/missions', {
5 method: 'POST',
6 headers: { 'Content-Type': 'application/json' },
7 body: JSON.stringify(newMission),
8 });
9
10 if (!response.ok) {
11 throw new Error('Failed to create mission');
12 }
13
14 return response.json();
15}
16
17function CreateMissionForm() {
18 const queryClient = useQueryClient();
19
20 const mutation = useMutation({
21 mutationFn: createMission,
22
23 // Optimistic update
24 onMutate: async (newMission) => {
25 await queryClient.cancelQueries({ queryKey: ['missions'] });
26 const previousMissions = queryClient.getQueryData(['missions']);
27 queryClient.setQueryData(['missions'], (old) => [...old, newMission]);
28 return { previousMissions };
29 },
30
31 // Rollback on error
32 onError: (err, newMission, context) => {
33 queryClient.setQueryData(['missions'], context.previousMissions);
34 },
35
36 // Invalidate and refetch
37 onSettled: () => {
38 queryClient.invalidateQueries({ queryKey: ['missions'] });
39 },
40 });
41
42 const handleSubmit = (e) => {
43 e.preventDefault();
44 const formData = new FormData(e.target);
45
46 mutation.mutate({
47 name: formData.get('name'),
48 destination: formData.get('destination'),
49 date: formData.get('date'),
50 });
51 };
52
53 return (
54 <form onSubmit={handleSubmit}>
55 <input name="name" placeholder="Nazwa misji" required />
56 <input name="destination" placeholder="Cel misji" required />
57 <input name="date" type="date" required />
58
59 <button type="submit" disabled={mutation.isPending}>
60 {mutation.isPending ? 'Tworzenie...' : 'Utwórz misję'}
61 </button>
62 </form>
63 );
64}1import { useQuery, keepPreviousData } from '@tanstack/react-query';
2
3function PaginatedMissions() {
4 const [page, setPage] = useState(1);
5
6 const { data, isLoading, isPlaceholderData } = useQuery({
7 queryKey: ['missions', page],
8 queryFn: () => fetchMissions(page),
9 placeholderData: keepPreviousData,
10 });
11
12 return (
13 <div>
14 {isLoading ? (
15 <div>Ładowanie...</div>
16 ) : (
17 <>
18 <div className="missions-list">
19 {data.missions.map(mission => (
20 <div key={mission.id} className="mission-item">
21 <h3>{mission.name}</h3>
22 </div>
23 ))}
24 </div>
25
26 <div className="pagination">
27 <button
28 onClick={() => setPage(old => Math.max(old - 1, 1))}
29 disabled={page === 1}
30 >
31 Poprzednia
32 </button>
33
34 <span>Strona {page} z {data.totalPages}</span>
35
36 <button
37 onClick={() => setPage(old => old + 1)}
38 disabled={isPlaceholderData || page >= data.totalPages}
39 >
40 Następna
41 </button>
42 </div>
43 </>
44 )}
45 </div>
46 );
47}1import { useInfiniteQuery } from '@tanstack/react-query';
2
3function InfiniteMissions() {
4 const {
5 data,
6 fetchNextPage,
7 hasNextPage,
8 isFetchingNextPage,
9 status,
10 } = useInfiniteQuery({
11 queryKey: ['infiniteMissions'],
12 queryFn: ({ pageParam = 1 }) => fetch(`/api/missions?page=${pageParam}&limit=20`).then(r => r.json()),
13 initialPageParam: 1,
14 getNextPageParam: (lastPage, allPages) => {
15 return lastPage.hasMore ? allPages.length + 1 : undefined;
16 },
17 });
18
19 if (status === 'pending') return <div>Ładowanie...</div>;
20 if (status === 'error') return <div>Błąd ładowania</div>;
21
22 return (
23 <div>
24 <div className="infinite-list">
25 {data.pages.map((page, i) => (
26 <React.Fragment key={i}>
27 {page.missions.map(mission => (
28 <div key={mission.id} className="mission-card">
29 <h3>{mission.name}</h3>
30 </div>
31 ))}
32 </React.Fragment>
33 ))}
34 </div>
35
36 <button
37 onClick={() => fetchNextPage()}
38 disabled={!hasNextPage || isFetchingNextPage}
39 >
40 {isFetchingNextPage
41 ? 'Ładowanie...'
42 : hasNextPage
43 ? 'Załaduj więcej'
44 : 'Brak więcej danych'}
45 </button>
46 </div>
47 );
48}React Query / TanStack Query rewolucjonizuje sposób, w jaki zarządzamy danymi serwerowymi w React. Podobnie jak autopilot w statku kosmicznym automatyzuje nawigację i koryguje kurs, React Query automatyzuje fetching, caching, synchronizację i aktualizacje danych, pozwalając nam skupić się na budowaniu świetnych doświadczeń użytkownika.