React Component State (State)

In the previous chapter, we learned about props, which allows us to pass data from parent components to child components. However, props are read-only. Components cannot change their own props.

So, when a component needs to change its own data based on user interactions, network responses, or other events, what should we do? The answer is State (State).

State is similar to props, but it is private and completely controlled by the component itself.

Introducing useState Hook

In function components, we use the useState Hook to add state to components.

What is a Hook? Hooks are special functions introduced in React 16.8. They are special functions that allow you to use state and other React features in function components. We will dive deeper into Hooks in later chapters.

Let's look at an example. We will create a component with a clock feature that updates the time itself.

Previously we used setInterval and root.render() to implement it, but that's not component-oriented thinking. Now, let the Clock component manage its own state.

import React, { useState, useEffect } from 'react';

function Clock() {
  // 1. Use useState to define a state variable named date
  const [date, setDate] = useState(new Date());

  // 2. Use useEffect to set up a timer
  useEffect(() => {
    const timerID = setInterval(() => {
      setDate(new Date()); // 3. Update state
    }, 1000);

    // Clear timer
    return () => {
      clearInterval(timerID);
    };
  }, []); // Empty array means this effect only runs once when component mounts

  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {date.toLocaleTimeString()}.</h2>
    </div>
  );
}

export default Clock;

Code Analysis

  1. const [date, setDate] = useState(new Date());

    • We call the useState Hook and pass in the initial state new Date().
    • useState returns an array containing two values:
      1. The current state value (date).
      2. A function that can be used to update the state (setDate).
    • We use array destructuring syntax [date, setDate] to name these two values.
  2. useEffect(...)

    • The useEffect Hook is used to handle side effects, such as data fetching, subscriptions, or manually modifying the DOM.
    • Here, we use it to set up a setInterval timer that runs every second.
    • The second parameter [] is a dependency array. When the array is empty, it means this effect will only execute once after the component is first rendered to the screen (mounted).
    • The function returned by useEffect is a cleanup function. It will execute when the component is removed from the screen (unmounted) to clear the timer and prevent memory leaks.
  3. setDate(new Date())

    • In the timer's callback function, we call setDate and pass in a new Date object.
    • This is the only correct way to update state.
    • When setDate is called, React will re-render the Clock component and use the latest date value.

State Updates are Asynchronous

When you call the setState function, React may batch multiple setState calls into a single update for performance optimization. This means this.props and this.state may be updated asynchronously, and you should not rely on their values to calculate the next state.

For example, the following code may not work as expected:

// Incorrect example
function Counter() {
  const [count, setCount] = useState(0);

  function handleIncrement() {
    setCount(count + 1); // setCount(0 + 1)
    setCount(count + 1); // still setCount(0 + 1)
    setCount(count + 1); // still setCount(0 + 1)
  }
  // ...
}

Because the count value doesn't change during the same render, the three calls to setCount have the same effect as one call.

The Correct Approach: Use Functional Updates

If you need to calculate the new state based on the previous state, you can pass a function to setState. This function will receive the previous state as a parameter and return the new state.

// Correct example
function Counter() {
  const [count, setCount] = useState(0);

  function handleIncrement() {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
  }
  // ...
}

Now, each call to setCount receives the latest state after the previous update, and count will eventually increase by 3.

Summary

  • state is used to store data that changes over time within a component.
  • Use useState Hook to add and manage state in function components.
  • Calling useState returns the current state and a function to update state [value, setValue].
  • Call the setValue function to update state, which triggers a component re-render.
  • State updates may be asynchronous; use functional updates when the new state depends on the old state.

Next, we will learn another important concept of components: lifecycle.