Wyobraź sobie, że Twoj statek kosmiczny wielokrotnie pyta Centrum Dowodzenia o te same dane o planetach. Za kazdym razem czekasz na odpowiedz, nawet jesli dane sie nie zmienily. To marnotrawstwo! Rozwiazaniem jest cachowanie -- przechowywanie danych lokalnie i odswiezanie ich w tle.
Bez cache:
Z cache:
SWR to strategia cachowania uzywana przez przegladarki i biblioteki. Nazwa mowi wszystko:
1// Wizualizacja SWR:
2// Krok 1: Uzytkownik wchodzi na strone
3// -> Cache istnieje? TAK -> Pokaz dane z cache (natychmiast!)
4// -> Jednoczesnie: wysylaj zapytanie do serwera
5
6// Krok 2: Serwer odpowiada
7// -> Dane sie zmienily? TAK -> Zaktualizuj widok i cache
8// -> Dane takie same? -> Nic nie rob
9
10// Krok 3: Uzytkownik wraca na strone
11// -> Powtorz od Kroku 1 (natychmiastowe wyswietlenie!)Najprostszy cache mozna zbudowac z obiektu
Map:1// Globalny cache - zyje poza komponentami
2const cache = new Map();
3
4function useFetchWithCache(url, ttl = 60000) {
5 const [data, setData] = useState(() => {
6 const cached = cache.get(url);
7 if (cached && Date.now() - cached.timestamp < ttl) {
8 return cached.data; // Natychmiastowe dane z cache!
9 }
10 return null;
11 });
12 const [loading, setLoading] = useState(!cache.has(url));
13 const [error, setError] = useState(null);
14
15 useEffect(() => {
16 const controller = new AbortController();
17
18 async function fetchData() {
19 try {
20 // Jesli mamy cache - nie pokazuj spinnera
21 if (!cache.has(url)) setLoading(true);
22 setError(null);
23
24 const response = await fetch(url, {
25 signal: controller.signal,
26 });
27 if (!response.ok) {
28 throw new Error(\`HTTP \${response.status}\`);
29 }
30 const result = await response.json();
31
32 // Zapisz w cache z timestampem
33 cache.set(url, {
34 data: result,
35 timestamp: Date.now(),
36 });
37 setData(result);
38 } catch (err) {
39 if (err.name !== 'AbortError') {
40 setError(err.message);
41 }
42 } finally {
43 setLoading(false);
44 }
45 }
46
47 fetchData();
48 return () => controller.abort();
49 }, [url, ttl]);
50
51 return { data, loading, error };
52}TTL okresla, jak dlugo dane w cache sa uznawane za aktualne:
1// TTL = 60000ms (1 minuta)
2// Dane starsze niz 1 minuta zostana pobrane ponownie
3
4// Krotki TTL (5-30s) - czesto zmieniajace sie dane
5// np. ceny kryptowalut, status misji
6const liveData = useFetchWithCache('/api/status', 5000);
7
8// Sredni TTL (1-5 min) - umiarkowanie zmienne dane
9// np. lista planet, katalog statkow
10const catalog = useFetchWithCache('/api/planets', 60000);
11
12// Dlugi TTL (10-60 min) - rzadko zmieniajace sie dane
13// np. konfiguracja, statyczne listy
14const config = useFetchWithCache('/api/config', 600000);Czasem musisz wymusic pobranie swiezych danych (np. po dodaniu nowego elementu):
1// Prosta invalidacja - usun z cache
2function invalidateCache(url) {
3 cache.delete(url);
4}
5
6// Po dodaniu nowej misji - invaliduj liste misji
7async function createMission(data) {
8 await fetch('/api/missions', {
9 method: 'POST',
10 headers: { 'Content-Type': 'application/json' },
11 body: JSON.stringify(data),
12 });
13
14 // Wymus odswiezenie listy misji
15 invalidateCache('/api/missions');
16}Zamiast czekac na serwer, mozemy zaktualizowac UI natychmiast:
1function MissionList() {
2 const [missions, setMissions] = useState([]);
3
4 const deleteMission = async (id) => {
5 // 1. OPTYMISTYCZNIE usun z UI (natychmiast!)
6 const previousMissions = [...missions];
7 setMissions(prev => prev.filter(m => m.id !== id));
8
9 try {
10 // 2. Wysylaj zapytanie DELETE do serwera
11 await fetch(\`/api/missions/\${id}\`, {
12 method: 'DELETE',
13 });
14 // Sukces - UI juz zaktualizowany!
15 } catch (error) {
16 // 3. BLAD - przywroc poprzedni stan (rollback)
17 setMissions(previousMissions);
18 alert('Nie udalo sie usunac misji');
19 }
20 };
21
22 return (
23 <ul>
24 {missions.map(m => (
25 <li key={m.id}>
26 {m.name}
27 <button onClick={() => deleteMission(m.id)}>
28 Usun
29 </button>
30 </li>
31 ))}
32 </ul>
33 );
34}W duzych projektach zamiast pisac wlasny cache, uzywamy biblioteki TanStack Query (dawniej React Query). To jak zaawansowany komputer pokladowy, ktory automatycznie zarzadza cala komunikacja:
1import {
2 useQuery,
3 useMutation,
4 useQueryClient,
5 QueryClient,
6 QueryClientProvider,
7} from '@tanstack/react-query';
8
9const queryClient = new QueryClient();
10
11function App() {
12 return (
13 <QueryClientProvider client={queryClient}>
14 <PlanetList />
15 </QueryClientProvider>
16 );
17}
18
19function PlanetList() {
20 // useQuery - automatyczny cache, SWR, retry, refetch
21 const { data, isLoading, error } = useQuery({
22 queryKey: ['planets'],
23 queryFn: () =>
24 fetch('https://swapi.dev/api/planets/')
25 .then(res => res.json()),
26 staleTime: 60000, // Dane swieze przez 1 minute
27 gcTime: 300000, // Usun z cache po 5 minutach
28 });
29
30 if (isLoading) return <p>Skanowanie galaktyki...</p>;
31 if (error) return <p>Blad: {error.message}</p>;
32
33 return (
34 <ul>
35 {data.results.map(planet => (
36 <li key={planet.name}>{planet.name}</li>
37 ))}
38 </ul>
39 );
40}1// 1. queryKey - unikalny identyfikator danych w cache
2// Zmiana klucza = nowe zapytanie
3const planets = useQuery({
4 queryKey: ['planets', page],
5 queryFn: () => fetchPlanets(page),
6});
7
8// 2. staleTime - jak dlugo dane sa swieze
9// staleTime: 0 - zawsze odswiezaj (domyslnie)
10// staleTime: 60000 - swieze przez 1 minute
11// staleTime: Infinity - nigdy nie odswiezaj
12
13// 3. gcTime (garbage collection) - kiedy usunac z cache
14// gcTime: 300000 - usun po 5 min nieaktywnosci
15
16// 4. Automatyczny refetch:
17// - Gdy okno wraca do fokusu
18// - Gdy siec wraca po rozlaczeniu
19// - W regularnych odstepach (refetchInterval)
20
21// 5. useMutation - operacje zapisu z invalidacja
22const mutation = useMutation({
23 mutationFn: (newMission) =>
24 fetch('/api/missions', {
25 method: 'POST',
26 body: JSON.stringify(newMission),
27 }),
28 onSuccess: () => {
29 // Po dodaniu misji - invaliduj cache listy misji
30 queryClient.invalidateQueries({
31 queryKey: ['missions']
32 });
33 },
34});Cache to supermoc kazdego kosmicznego nawigatora -- dane sa dostepne natychmiast, serwer nie jest przeciazony, a uzytkownik nigdy nie czeka na dane, ktore juz widzial!