On a cosmic journey through the React galaxy, state is like the rocket fuel powering the interactivity of your components. Without state, components would be static objects - like planets without an atmosphere. State allows components to "remember" information and react to changes, making your applications alive and responsive.
State is a JavaScript object that stores data specific to a component. Unlike props, which are passed to the component, state is managed inside the component and can be changed directly by the component itself.
Imagine a spaceship control panel. Props are like set destination coordinates - you cannot change them from the panel level. State, on the other hand, is like the current speed, altitude, and temperature readings - data that constantly changes and is controlled by the panel itself.
In modern React, state in functional components is managed using the
useState hook. This simple yet powerful hook allows you to add state to functional components.1import React, { useState } from 'react';
2
3function ControlPanel() {
4 // Declaring a state variable "fuel" with initial value 100
5 const [fuel, setFuel] = useState(100);
6
7 // fuel - the current state value
8 // setFuel - the function to update state
9
10 return (
11 <div className="control-panel">
12 <h2>Control Panel</h2>
13 <p>Fuel Level: {fuel}%</p>
14 <button onClick={() => setFuel(fuel - 10)}>
15 Use Fuel
16 </button>
17 </div>
18 );
19}In the example above:
useState hook from ReactuseState(100) to create a state variable fuel with an initial value of 100fuel: the current state valuesetFuel: the function to update that valuesetFuel in the click event handler to decrease the fuel value by 10A component can have multiple independent state variables:
1function SpaceshipDashboard() {
2 const [fuel, setFuel] = useState(100);
3 const [speed, setSpeed] = useState(0);
4 const [shields, setShields] = useState(true);
5 const [destination, setDestination] = useState('Orbit');
6
7 return (
8 <div className="dashboard">
9 <h2>Spaceship Dashboard</h2>
10 <div className="gauges">
11 <div className="gauge">
12 <h3>Fuel</h3>
13 <p>{fuel}%</p>
14 <button onClick={() => setFuel(Math.max(0, fuel - 5))}>
15 Increase Thrust
16 </button>
17 </div>
18
19 <div className="gauge">
20 <h3>Speed</h3>
21 <p>{speed} km/s</p>
22 <button onClick={() => setSpeed(speed + 1)}>
23 Accelerate
24 </button>
25 <button onClick={() => setSpeed(Math.max(0, speed - 1))}>
26 Decelerate
27 </button>
28 </div>
29
30 <div className="gauge">
31 <h3>Shields</h3>
32 <p>{shields ? 'Enabled' : 'Disabled'}</p>
33 <button onClick={() => setShields(!shields)}>
34 Toggle
35 </button>
36 </div>
37
38 <div className="gauge">
39 <h3>Destination</h3>
40 <p>{destination}</p>
41 <select
42 value={destination}
43 onChange={(e) => setDestination(e.target.value)}
44 >
45 <option value="Orbit">Earth Orbit</option>
46 <option value="Moon">Moon</option>
47 <option value="Mars">Mars</option>
48 <option value="Asteroid Belt">Asteroid Belt</option>
49 </select>
50 </div>
51 </div>
52 </div>
53 );
54}State can be a simple value (number, string, boolean), but it can also be a more complex structure, like an array or object:
1function CrewManagement() {
2 const [crew, setCrew] = useState([
3 { id: 1, name: 'Anna Nowak', role: 'Captain', onDuty: true },
4 { id: 2, name: 'Jan Kowalski', role: 'Navigator', onDuty: false },
5 { id: 3, name: 'Maria Wisniewska', role: 'Engineer', onDuty: true }
6 ]);
7
8 const toggleDuty = (crewId) => {
9 setCrew(crew.map(member =>
10 member.id === crewId
11 ? { ...member, onDuty: !member.onDuty }
12 : member
13 ));
14 };
15
16 return (
17 <div className="crew-management">
18 <h2>Crew Management</h2>
19 <ul className="crew-list">
20 {crew.map(member => (
21 <li key={member.id} className={member.onDuty ? 'on-duty' : 'off-duty'}>
22 <strong>{member.name}</strong> - {member.role}
23 <button onClick={() => toggleDuty(member.id)}>
24 {member.onDuty ? 'End Shift' : 'Start Shift'}
25 </button>
26 </li>
27 ))}
28 </ul>
29 </div>
30 );
31}In this example, the
crew state is an array of objects. The toggleDuty function uses map to create a new array with the updated state of a specific crew member.In React, state works like a snapshot. When React renders a component, it "captures" the state at that point in time, and all values inside the rendering function (including event handlers) will "see" the same state snapshot.
Consider this example:
1function RocketLauncher() {
2 const [countdown, setCountdown] = useState(10);
3
4 const decrementCountdown = () => {
5 // This will NOT work as expected if we want to
6 // perform multiple decrements in a single render cycle
7 setCountdown(countdown - 1); // Uses the same value from the snapshot
8 setCountdown(countdown - 1); // Still uses the same value!
9 };
10
11 return (
12 <div className="rocket-launcher">
13 <h2>Countdown to Launch: {countdown}</h2>
14 <button onClick={decrementCountdown}>
15 Decrease Countdown
16 </button>
17 </div>
18 );
19}In the example above, despite calling
setCountdown twice, the value will only decrease by 1, not by 2. This happens because both updates "see" the same countdown value from the snapshot.To solve the snapshot problem, React allows you to pass a function to the state updater function. This function receives the previous state as an argument:
1function RocketLauncher() {
2 const [countdown, setCountdown] = useState(10);
3
4 const decrementCountdown = () => {
5 // This will work correctly, even for multiple updates
6 setCountdown(prevCount => prevCount - 1); // Based on the previous value
7 setCountdown(prevCount => prevCount - 1); // Based on the result of the previous update
8 };
9
10 return (
11 <div className="rocket-launcher">
12 <h2>Countdown to Launch: {countdown}</h2>
13 <button onClick={decrementCountdown}>
14 Decrease Countdown
15 </button>
16 </div>
17 );
18}Now, with each button click, the countdown will decrease by 2.
When updating objects in state, it is important to always create a new object rather than modifying the existing one. This is in line with the immutability principles in React:
1function SpaceshipSystems() {
2 const [systems, setSystems] = useState({
3 engines: 'offline',
4 lifeSupportOutput: 100,
5 navigationStatus: 'calibrating',
6 shields: 85
7 });
8
9 const startEngines = () => {
10 // WRONG - modifying the original object
11 // systems.engines = 'online';
12 // setSystems(systems);
13
14 // CORRECT - creating a new object
15 setSystems({
16 ...systems, // copies all existing properties
17 engines: 'online' // overrides only the one we want to change
18 });
19 };
20
21 return (
22 <div className="spaceship-systems">
23 <h2>Systems Status</h2>
24 <p>Engines: {systems.engines}</p>
25 <p>Life Support: {systems.lifeSupportOutput}%</p>
26 <p>Navigation: {systems.navigationStatus}</p>
27 <p>Shields: {systems.shields}%</p>
28
29 <button onClick={startEngines}>
30 Start Engines
31 </button>
32 </div>
33 );
34}Just like with objects, when updating arrays you must also follow immutability principles:
1function MissionLog() {
2 const [logs, setLogs] = useState([
3 { id: 1, time: '08:00', message: 'Mission started' }
4 ]);
5
6 const addLog = () => {
7 const newLog = {
8 id: logs.length + 1,
9 time: new Date().toLocaleTimeString(),
10 message: 'New log entry'
11 };
12
13 // WRONG - mutating the original array
14 // logs.push(newLog);
15 // setLogs(logs);
16
17 // CORRECT - creating a new array
18 setLogs([...logs, newLog]); // adding to the end
19 // or
20 // setLogs([newLog, ...logs]); // adding to the beginning
21 };
22
23 const removeLog = (logId) => {
24 // Filtering - creating a new array without the removed element
25 setLogs(logs.filter(log => log.id !== logId));
26 };
27
28 return (
29 <div className="mission-log">
30 <h2>Mission Log</h2>
31 <button onClick={addLog}>Add New Entry</button>
32
33 <ul className="log-entries">
34 {logs.map(log => (
35 <li key={log.id}>
36 <span className="time">{log.time}</span>
37 <span className="message">{log.message}</span>
38 <button onClick={() => removeLog(log.id)}>Remove</button>
39 </li>
40 ))}
41 </ul>
42 </div>
43 );
44}State should be used for data that:
Not all data should be in state. Good questions to ask yourself:
State is a key element of React components that allows you to:
In the following sections, we will see how to handle events in React and how to combine them with state management to create fully interactive applications.