We use cookies to enhance your experience on the site
CodeWorlds

Component State (State)

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.

What is component state?

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.

Managing state in functional components

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.

Basic useState syntax

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:

  1. We import the
    useState
    hook from React
  2. We call
    useState(100)
    to create a state variable
    fuel
    with an initial value of 100
  3. We destructure the result to get:
    • fuel
      : the current state value
    • setFuel
      : the function to update that value
  4. We use
    setFuel
    in the click event handler to decrease the fuel value by 10

Using multiple state variables

A 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}

Complex state structures

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.

State as a snapshot

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.

Updating state based on previous state

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.

Updating objects in state

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}

Updating arrays in state

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}

When to use state?

State should be used for data that:

  1. Changes over time
  2. Affects the rendering of the component
  3. Cannot be computed from props or other state

Not all data should be in state. Good questions to ask yourself:

  • Does the data come from a parent via props? If so, it probably should not be in state.
  • Does the data remain unchanged over time? If so, it probably should not be in state.
  • Can the data be computed from existing props or state? If so, it definitely should not be in state.

Summary

State is a key element of React components that allows you to:

  • Store data specific to the component
  • Manage data that changes over time
  • Create interactive user interfaces that respond to user actions

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.

Go to CodeWorlds