W kosmicznej podróży przez React, wielokrotne pisanie tych samych wzorców useEffect w różnych komponentach to jak budowanie od zera identycznych systemów nawigacyjnych dla każdego statku we flocie. Custom Hooks pozwalają wyekstrahować powtarzalną logikę efektów do jednej, wielokrotnie używanej funkcji - jak stworzenie modułu napędowego, który można zainstalować w dowolnym statku.
Custom Hook to zwykła funkcja JavaScript, której nazwa zaczyna się od
use i która może wywoływać inne hooki (useState, useEffect, useRef itp.). Nie jest to żadna specjalna składnia Reacta - jedyną konwencją jest prefiks use.1// Custom Hook - wyekstrahowana logika
2function useWindowSize() {
3 const [size, setSize] = useState({
4 width: window.innerWidth,
5 height: window.innerHeight
6 });
7
8 useEffect(() => {
9 const handleResize = () => {
10 setSize({ width: window.innerWidth, height: window.innerHeight });
11 };
12
13 window.addEventListener('resize', handleResize);
14 return () => window.removeEventListener('resize', handleResize);
15 }, []);
16
17 return size;
18}
19
20// Użycie w komponencie - czysto i prosto
21function SpaceshipCockpit() {
22 const { width, height } = useWindowSize();
23
24 return (
25 <p>
26 Rozmiar ekranu kokpitu: {width} x {height}
27 </p>
28 );
29}Zauważ, że
useWindowSize zawiera wewnątrz useState i useEffect z cleanup - cała logika jest zamknięta w jednym hooku, a komponent dostaje gotowy wynik.Jednym z najczęstszych custom hooków jest hook do pobierania danych. Zamiast powtarzać wzorzec loading/error/data w każdym komponencie, zamykamy go w
useFetch:1function useFetch(url) {
2 const [data, setData] = useState(null);
3 const [isLoading, setIsLoading] = useState(true);
4 const [error, setError] = useState(null);
5
6 useEffect(() => {
7 const controller = new AbortController();
8 setIsLoading(true);
9 setError(null);
10
11 fetch(url, { signal: controller.signal })
12 .then(response => {
13 if (!response.ok) throw new Error('Blad sieci');
14 return response.json();
15 })
16 .then(data => {
17 setData(data);
18 setIsLoading(false);
19 })
20 .catch(err => {
21 if (err.name !== 'AbortError') {
22 setError(err.message);
23 setIsLoading(false);
24 }
25 });
26
27 // Cleanup - anuluj request przy odmontowaniu lub zmianie URL
28 return () => controller.abort();
29 }, [url]);
30
31 return { data, isLoading, error };
32}Teraz każdy komponent potrzebujący danych z API może użyć jednej linii:
1function CrewList() {
2 const { data: crew, isLoading, error } = useFetch('/api/crew');
3
4 if (isLoading) return <p>Ladowanie zalogi...</p>;
5 if (error) return <p>Blad: {error}</p>;
6
7 return (
8 <ul>
9 {crew.map(member => (
10 <li key={member.id}>{member.name}</li>
11 ))}
12 </ul>
13 );
14}
15
16function MissionLog() {
17 const { data: missions, isLoading } = useFetch('/api/missions');
18
19 if (isLoading) return <p>Ladowanie misji...</p>;
20
21 return missions.map(m => <div key={m.id}>{m.name}</div>);
22}Dwa komponenty, ten sam wzorzec pobierania danych - zero duplikacji kodu.
Timer z setInterval wymaga cleanup i poprawnej obsługi zależności. Custom hook zamyka tę logikę:
1function useInterval(callback, delay) {
2 const savedCallback = useRef();
3
4 // Zapamiętaj najnowszą wersję callback
5 useEffect(() => {
6 savedCallback.current = callback;
7 }, [callback]);
8
9 // Ustaw timer
10 useEffect(() => {
11 if (delay === null) return; // null = pauzuj timer
12
13 const tick = () => savedCallback.current();
14 const id = setInterval(tick, delay);
15 return () => clearInterval(id);
16 }, [delay]);
17}
18
19// Uzycie
20function MissionClock() {
21 const [seconds, setSeconds] = useState(0);
22 const [isRunning, setIsRunning] = useState(false);
23
24 useInterval(() => {
25 setSeconds(prev => prev + 1);
26 }, isRunning ? 1000 : null); // null = pauzuj
27
28 return (
29 <div>
30 <p>Czas misji: {seconds}s</p>
31 <button onClick={() => setIsRunning(!isRunning)}>
32 {isRunning ? 'Pauza' : 'Start'}
33 </button>
34 </div>
35 );
36}Dzięki
useRef dla callback, timer zawsze wywołuje najnowszą wersję funkcji bez konieczności restartowania interwału.Hook synchronizujący stan z localStorage:
1function useLocalStorage(key, initialValue) {
2 const [storedValue, setStoredValue] = useState(() => {
3 try {
4 const item = window.localStorage.getItem(key);
5 return item ? JSON.parse(item) : initialValue;
6 } catch (error) {
7 return initialValue;
8 }
9 });
10
11 useEffect(() => {
12 try {
13 window.localStorage.setItem(key, JSON.stringify(storedValue));
14 } catch (error) {
15 console.error('Blad zapisu do localStorage:', error);
16 }
17 }, [key, storedValue]);
18
19 return [storedValue, setStoredValue];
20}
21
22// Uzycie - dziala jak useState, ale persystuje dane
23function ShipSettings() {
24 const [theme, setTheme] = useLocalStorage('theme', 'dark');
25 const [volume, setVolume] = useLocalStorage('volume', 50);
26
27 return (
28 <div>
29 <select value={theme} onChange={e => setTheme(e.target.value)}>
30 <option value="dark">Ciemny</option>
31 <option value="light">Jasny</option>
32 </select>
33 <input
34 type="range"
35 value={volume}
36 onChange={e => setVolume(Number(e.target.value))}
37 />
38 </div>
39 );
40}Po odswiezeniu strony ustawienia sa zachowane - localStorage pamięta wartości między sesjami.
use - to nie jest opcjonalne. React wymaga tego prefiksu, aby móc sprawdzać reguły hooków1// Custom Hook z wieloma wartościami zwracanymi
2function useToggle(initialValue = false) {
3 const [value, setValue] = useState(initialValue);
4
5 const toggle = () => setValue(prev => !prev);
6 const setTrue = () => setValue(true);
7 const setFalse = () => setValue(false);
8
9 return { value, toggle, setTrue, setFalse };
10}
11
12// Uzycie
13function ShieldControl() {
14 const shields = useToggle(false);
15
16 return (
17 <div>
18 <p>Tarcze: {shields.value ? 'AKTYWNE' : 'WYLACZONE'}</p>
19 <button onClick={shields.toggle}>Przelacz</button>
20 <button onClick={shields.setTrue}>Aktywuj</button>
21 <button onClick={shields.setFalse}>Dezaktywuj</button>
22 </div>
23 );
24}Custom Hooks to jedno z najpotężniejszych narzędzi w React do organizacji kodu:
Jak modułowe systemy statku kosmicznego - system nawigacyjny, system komunikacyjny, system obronny - każdy jest osobnym modułem (hookiem), który można zainstalować w dowolnym statku (komponencie).