React useState Hook & Immutable State


03-09-2023

useState is a hook in React that allows you to add state to your functional components. It is used when you need to store and manage state data in your application, such as user input, API responses, or to show/hide components.

In React, state refers to the data that determines the behavior of your application and can change over time. By using useState, you can define a piece of state data and a function that updates that data. When the state data is updated, React automatically re-renders the component and any child components that depend on that data.

When state data changes in a component, React uses its virtual DOM to determine what parts of the real DOM need to be updated. React compares the previous state of the virtual DOM with the current state to identify any differences. This process is known as reconciliation.

Here's an example of using the useState hook to manage the visibility of a modal dialog box:

import React, { useState } from 'react';

function ModalButton() {
	const [showModal, setShowModal] = useState(false);

	const handleOpenModal = () => setShowModal(true);
	const handleCloseModal = () => setShowModal(false);

	return (
		<div> {/* always visible */}
			<button onClick={handleOpenModal}>Open Modal</button>
	{/* expression must evaluate to true && true to show */}
			{showModal && (
				<> 
					<div>
						<h2>Modal Title</h2>
						<p>Modal content goes here</p>
						<button onClick={handleCloseModal}>Close</button>
					</div>
				</>
			)}
		</div>
	);
}

An event handler is supposed to be either a function or a function reference. A function call will not work here.

Mutable & Immutable State


When working with the useState hook, it's important to understand the difference between mutable and immutable state. Mutable state is state that can be changed directly, while immutable state is state that cannot be changed directly but must be replaced with a new version.

In React, it's generally recommended to use immutable state as much as possible, as it helps to avoid bugs and make your code more predictable. This means that instead of directly modifying state, you should create a new copy of the state with the desired changes.

Let's say we have a simple React component that displays a list of items and allows the user to add new items to the list. We could implement this component with either mutable or immutable state.

Here's an example of the component using mutable state:

import React, { useState } from 'react';

function MutableList() {
  const [items, setItems] = useState(['First Item']);

  function handleAddItem() {
    items.push('New Item'); {/* Directly modifying the state array */}
    setItems(items); // {/* Updating the state with the modified array */}
  }

  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={Math.random()}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
}

We directly modify the items array by using the push method to add a new item. This can lead to unexpected behavior and bugs, especially if other components are also modifying the same array.

And here's an example of the same component using immutable state:

function ImmutableList() {
  const [items, setItems] = useState([]);

  function handleAddItem() {
{/* Use the spread syntax (...) and make a copy of the original state */}
    const newItems = [...items, 'New Item']; 
    setItems(newItems); {/* Update the state with the new array */}
  }

  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={Math.random()}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
}

Overall, using immutable state gives you control over your code, helps to avoid bugs, and makes your code more maintainable.