On our cosmic journey through React, repeatedly writing the same useEffect patterns in different components is like building identical navigation systems from scratch for every ship in the fleet. Custom Hooks allow you to extract repeatable effect logic into a single, reusable function - like creating a propulsion module that can be installed in any ship.
A Custom Hook is a regular JavaScript function whose name starts with
use and which can call other hooks (useState, useEffect, useRef, etc.). It's not any special React syntax - the only convention is the use prefix.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// Usage in a component - clean and simple
21function SpaceshipCockpit() {
22 const { width, height } = useWindowSize();
23
24 return (
25 <p>
26 Cockpit screen size: {width} x {height}
27 </p>
28 );
29}Notice that
useWindowSize contains useState and useEffect with cleanup inside - all the logic is enclosed in a single hook, and the component gets a ready result.One of the most common custom hooks is a data fetching hook. Instead of repeating the loading/error/data pattern in every component, we encapsulate it in
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('Network error');
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 - cancel request on unmount or URL change
28 return () => controller.abort();
29 }, [url]);
30
31 return { data, isLoading, error };
32}Now any component that needs API data can use a single line:
1function CrewList() {
2 const { data: crew, isLoading, error } = useFetch('/api/crew');
3
4 if (isLoading) return <p>Loading crew...</p>;
5 if (error) return <p>Error: {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>Loading missions...</p>;
20
21 return missions.map(m => <div key={m.id}>{m.name}</div>);
22}Two components, same data fetching pattern - zero code duplication.
A timer with setInterval requires cleanup and proper dependency handling. A custom hook encapsulates this logic:
1function useInterval(callback, delay) {
2 const savedCallback = useRef();
3
4 // Remember the latest callback version
5 useEffect(() => {
6 savedCallback.current = callback;
7 }, [callback]);
8
9 // Set timer
10 useEffect(() => {
11 if (delay === null) return; // null = pause timer
12
13 const tick = () => savedCallback.current();
14 const id = setInterval(tick, delay);
15 return () => clearInterval(id);
16 }, [delay]);
17}
18
19// Usage
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>Mission time: {seconds}s</p>
31 <button onClick={() => setIsRunning(!isRunning)}>
32 {isRunning ? 'Pause' : 'Start'}
33 </button>
34 </div>
35 );
36}Thanks to
useRef for the callback, the timer always calls the latest version of the function without needing to restart the interval.A hook that synchronizes state with 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('Error writing to localStorage:', error);
16 }
17 }, [key, storedValue]);
18
19 return [storedValue, setStoredValue];
20}
21
22// Usage - works like useState, but persists data
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">Dark</option>
31 <option value="light">Light</option>
32 </select>
33 <input
34 type="range"
35 value={volume}
36 onChange={e => setVolume(Number(e.target.value))}
37 />
38 </div>
39 );
40}After refreshing the page, settings are preserved - localStorage remembers values between sessions.
use - this is not optional. React requires this prefix to enforce hook rules1// Custom Hook with multiple return values
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// Usage
13function ShieldControl() {
14 const shields = useToggle(false);
15
16 return (
17 <div>
18 <p>Shields: {shields.value ? 'ACTIVE' : 'DISABLED'}</p>
19 <button onClick={shields.toggle}>Toggle</button>
20 <button onClick={shields.setTrue}>Activate</button>
21 <button onClick={shields.setFalse}>Deactivate</button>
22 </div>
23 );
24}Custom Hooks are one of the most powerful tools in React for code organization:
Like modular spaceship systems - navigation system, communication system, defense system - each is a separate module (hook) that can be installed in any ship (component).