DEV Community

Ayako yk
Ayako yk

Posted on

Understanding State in React: Managing State Between Components

In my previous blog post, I covered the fundamentals of state in React, including concepts like state as a snapshot, batching, and updating states. In this post, I'll dive deeper into state management, focusing on how to manage state between components.

  1. Declarative vs. Imperative
  2. Lifting State Up
  3. React's State and the Render Tree
  4. Component State and React's Behavior
  5. State Preservation
  6. State Destruction
  7. Rendering Different Components
  8. Using "key" to Preserve State

Declarative vs. Imperative
React is declarative, not imperative. This means that instead of telling React exactly how to update the UI, you simply declare what the UI should look like based on the current state. React then takes care of the rest.

For example, if the state isOpen is true, React will display a hamburger menu; if the state is false, it will hide the menu.

Lifting State Up
When two separate components need to share and manage the same state, it's a good practice to "lift" the state up. This means moving the state to their closest common parent component and passing it down to the child components via props. This achieves a single source of truth for the state.

Components with props passed down are called controlled, while components that manage their own states are called uncontrolled.

Component State and React's Behavior
When you render the same component multiple times, each instance of the component is independent and holds its own state, which doesn't affect the other instances.

Here's an example from React's documentation:
(The code has been simplified for clarity.)

export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  return (
    <div>
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here's an image of the React component tree.

State Preservation
React keeps the state for as long as the same component is re-rendered at the same position in the render tree.

State Destruction
When the component is unmounted, the state is destroyed. When it is mounted again, the state is initialized.

The "Render the second counter" checkbox removes the second component and creates a new one.

Example:

If the same component is rendered at the same position (e.g., by conditional state), the state is preserved.

Example:

export default function App() {
  const [isFancy, setIsFancy] = useState(false); // Fancy adds fancy styles
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Rendering Different Components
Rendering different components at the same position resets the state, as React treats them as separate instances.
In this example, different components are rendered depending on the condition: one is <p>, and the other is <Count />. This will reset the state.

export default function App() {
  const [isPaused, setIsPaused] = useState(false);
  return (
    <div>
      {isPaused ? (
        <p>See you later!</p> 
      ) : (
        <Counter /> 
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using key to Preserve State
To preserve the state for each component, we can either render them at different positions in the tree or assign a unique key to each component.

The use case is when we want to keep separate counters for different people: Person A's counter and Person B's counter. We don't want to display Person A's counter for Person B. By giving a unique key to each component, React can distinguish between them.
Here's an example from the React documentation:

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
      ) : (
        <Counter key="Sarah" person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Note that when switching from Person B back to Person A, the state is reinitialized.

The latest React documentation explains things so well that it gave me a much clearer understanding of how React treats state. I only wish I had known this when I first started learning React. I hope you found this article helpful, and I encourage you to visit the React documentation for more details and further practice (it also offers challenges to deepen your understanding).

Top comments (0)