Wyobraź sobie, że systemy nawigacyjne twojego statku kosmicznego działają na podstawie danych o stanie - prędkość, kurs, poziom paliwa. Każda aktualizacja tych parametrów musi być precyzyjna, bo nawet drobny błąd może wytrącić statek z orbity. W React aktualizacja stanu podlega podobnym zasadom - musisz znać wzorce bezpiecznej aktualizacji, aby twoja aplikacja działała przewidywalnie.
Gdy wywołujesz
setState, masz dwie opcje. Pierwsza to przekazanie nowej wartości wprost:1setCount(count + 1);Druga to aktualizacja funkcyjna — przekazanie funkcji, która otrzymuje poprzednią wartość stanu:
1setCount(prev => prev + 1);Dlaczego to ma znaczenie? React grupuje aktualizacje stanu (batching) i nie wykonuje ich natychmiast. Jeśli w jednym handlerze wywołasz
setCount(count + 1) kilka razy, każde wywołanie zobaczy tę samą wartość count:1function FuelGauge() {
2 const [fuel, setFuel] = useState(50);
3
4 const handleTripleBoost = () => {
5 // ❌ Wszystkie trzy widzą tę samą wartość fuel!
6 // Wynik: fuel wzrośnie tylko o 10, nie o 30
7 setFuel(fuel + 10);
8 setFuel(fuel + 10);
9 setFuel(fuel + 10);
10 };
11
12 const handleTripleBoostCorrect = () => {
13 // ✅ Każde wywołanie otrzymuje aktualną wartość
14 // Wynik: fuel wzrośnie o 30
15 setFuel(prev => prev + 10);
16 setFuel(prev => prev + 10);
17 setFuel(prev => prev + 10);
18 };
19
20 return (
21 <div>
22 <p>Paliwo: {fuel}%</p>
23 <button onClick={handleTripleBoost}>Boost (błędny)</button>
24 <button onClick={handleTripleBoostCorrect}>Boost (poprawny)</button>
25 </div>
26 );
27}Zasada: Zawsze używaj aktualizacji funkcyjnej (
prev => ...), gdy nowa wartość zależy od poprzedniej.Masz dwie strategie przechowywania stanu w komponencie. Pierwsza to oddzielne zmienne stanu:
1function ShipDashboard() {
2 const [speed, setSpeed] = useState(0);
3 const [altitude, setAltitude] = useState(0);
4 const [heading, setHeading] = useState(0);
5
6 // Każda zmienna aktualizowana niezależnie
7 const accelerate = () => setSpeed(prev => prev + 10);
8 const climb = () => setAltitude(prev => prev + 100);
9
10 return (
11 <div>
12 <p>Prędkość: {speed} km/s | Wysokość: {altitude} km | Kurs: {heading}°</p>
13 <button onClick={accelerate}>Przyspiesz</button>
14 <button onClick={climb}>Wznieś się</button>
15 </div>
16 );
17}Druga to stan w obiekcie — przydatna gdy dane są ze sobą powiązane:
1function ShipDashboard() {
2 const [navigation, setNavigation] = useState({
3 speed: 0,
4 altitude: 0,
5 heading: 0
6 });
7
8 // WAŻNE: zawsze kopiuj cały obiekt operatorem spread!
9 const accelerate = () => {
10 setNavigation(prev => ({ ...prev, speed: prev.speed + 10 }));
11 };
12
13 return (
14 <div>
15 <p>Prędkość: {navigation.speed} km/s</p>
16 <button onClick={accelerate}>Przyspiesz</button>
17 </div>
18 );
19}Kiedy co stosować?
useState — gdy wartości zmieniają się niezależnie od siebieuseState — gdy wartości logicznie należą do jednej całości (np. formularz, pozycja x/y)W React stan jest niemutowalny (immutable). Nigdy nie modyfikuj stanu bezpośrednio — zawsze twórz nową kopię. To jak zasada bezpieczeństwa na statku kosmicznym: nie modyfikujesz dziennika pokładowego, tylko dopisujesz nowy wpis.
1function MissionLog() {
2 const [logs, setLogs] = useState(["Start misji"]);
3
4 const addLog = (entry) => {
5 // ❌ BŁĄD: push mutuje istniejącą tablicę!
6 // React nie wykryje zmiany i nie przerenderuje komponentu
7 logs.push(entry);
8 setLogs(logs);
9 };
10
11 const addLogCorrect = (entry) => {
12 // ✅ Spread tworzy nową tablicę
13 setLogs(prev => [...prev, entry]);
14 };
15
16 return (
17 <div>
18 <button onClick={() => addLogCorrect("Nowy wpis")}>
19 Dodaj wpis
20 </button>
21 {logs.map((log, i) => <p key={i}>{log}</p>)}
22 </div>
23 );
24}1function CrewMember() {
2 const [member, setMember] = useState({
3 name: "Kapitan Nova",
4 rank: "Kapitan",
5 missions: 12
6 });
7
8 const promote = () => {
9 // ❌ BŁĄD: bezpośrednia mutacja obiektu
10 member.rank = "Admirał";
11 setMember(member);
12 };
13
14 const promoteCorrect = () => {
15 // ✅ Tworzenie nowego obiektu z operatorem spread
16 setMember(prev => ({ ...prev, rank: "Admirał" }));
17 };
18
19 return (
20 <div>
21 <p>{member.name} — {member.rank}</p>
22 <button onClick={promoteCorrect}>Awansuj</button>
23 </div>
24 );
25}Closure (domknięcie) to sytuacja, gdy funkcja "zapamiętuje" zmienne z momentu jej utworzenia. W React może to prowadzić do odczytu nieaktualnego stanu:
1function CountdownTimer() {
2 const [seconds, setSeconds] = useState(10);
3
4 const startCountdown = () => {
5 // ❌ seconds jest "zamrożone" z chwili kliknięcia!
6 // Każdy interwał widzi tę samą wartość
7 setInterval(() => {
8 setSeconds(seconds - 1); // zawsze 10 - 1 = 9
9 }, 1000);
10 };
11
12 const startCountdownCorrect = () => {
13 // ✅ Aktualizacja funkcyjna zawsze używa aktualnej wartości
14 setInterval(() => {
15 setSeconds(prev => prev - 1);
16 }, 1000);
17 };
18
19 return (
20 <div>
21 <p>Odliczanie: {seconds}s</p>
22 <button onClick={startCountdownCorrect}>Start</button>
23 </div>
24 );
25}React 18+ automatycznie grupuje (batchuje) wiele aktualizacji stanu w jednym event handlerze, wykonując tylko jeden re-render. To jak komputer pokładowy, który zbiera wszystkie raporty z czujników i aktualizuje wyświetlacz raz, zamiast odświeżać go po każdym czujniku:
1function NavigationUpdate() {
2 const [position, setPosition] = useState({ x: 0, y: 0 });
3 const [velocity, setVelocity] = useState(0);
4 const [status, setStatus] = useState("idle");
5
6 const handleJump = () => {
7 // React grupuje te 3 aktualizacje w jeden render
8 setPosition({ x: 100, y: 200 });
9 setVelocity(9999);
10 setStatus("hyperjump");
11 // Komponent renderuje się JEDEN raz, nie trzy!
12 };
13
14 return (
15 <div>
16 <p>Pozycja: ({position.x}, {position.y})</p>
17 <p>Prędkość: {velocity} | Status: {status}</p>
18 <button onClick={handleJump}>Skok nadprzestrzenny</button>
19 </div>
20 );
21}Oto kilka wzorców, które przyda ci się znać na pokładzie statku React:
1// Usuwanie elementu z tablicy
2setItems(prev => prev.filter(item => item.id !== idToRemove));
3
4// Aktualizacja jednego elementu w tablicy
5setItems(prev => prev.map(item =>
6 item.id === targetId ? { ...item, status: "done" } : item
7));
8
9// Toggle (przełączanie boolean)
10setIsActive(prev => !prev);
11
12// Dodanie elementu na początek tablicy
13setMessages(prev => [newMessage, ...prev]);
14
15// Inkrementacja z limitem
16setFuel(prev => Math.min(prev + 10, 100));Aktualizacja stanu w React — kluczowe zasady:
prev => prev + 1) — używaj gdy nowa wartość zależy od poprzedniejPamiętaj: Stan w React to jak dziennik pokładowy twojego statku — nigdy go nie edytujesz, tylko tworzysz nowy wpis na podstawie poprzedniego!