Adding Interactivity With React

Adding Interactivity With React

March 12, 20246 min read

Some things on the screen update in response to user input. For example, clicking on an image gallery switches the active image. In React, the data that changes over time is called state. The developer can add state to any component, and update it as needed. In this chapter, the developer will learn how to write components that handle interactions, update their state, and display different output over time.

1. Responding to Events

React lest the developer add event handlers to the JSX. Event handlers are custom functions that will be triggered in response to user interactions like clicking, hovering, focusing on form inputs, and so on.

Built-in components like <button> only support built-in events like onClick. However, the developer can create their own components, and give their event handler props any application-specific names.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}
 
function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}
 
function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

2. State - A Component's Memory

Components often need to change what is on the screen as a result of an interaction. Typing into the form should update the input field, clicking "next" on an image carousel should change which image is displayed, clicking "buy" puts the product in the shopping cart. Components need to "remember" things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called state.

The developer can add state to a component with a useState Hook. Hooks are special functions that let components use React features (state is one of those features). The useState Hook lets the developer declare a state variable. It takes the initial state and returns a pair of values: the current state, and a state setter function that lets the developer update it.

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);

Here is how an image gallery uses and updates state on click:

import { useState } from 'react';
import { sculpltureLIst } from './data.js';
 
export default function Gallery() {
	const [index, setIndex] = useState(0);
	const [showMore, setShowMore] = useState(false);
	const hasNext = index < sculptureList.length - 1;
 
	function handleNextClick() {
		if (hasNext) {
			setIndex(index + 1);
		} else {
			setIndex{0}
		}
	}
 
	function handleMoreClick() {
		setShowMore(!showMore);
	}
 
	let sculplture = sculpltureList[index];
 
	return (
		<>
			<button onClick={handleNextClick}>
				Next
			</button>
			<h2>
				<i>{sculpture.name}</i>
				by {sculpture.artist}
			</h2>
			<h3>
				({index + 1} of {sculpture.length})
			</h3>
			<button onClick={handleMoreClick}>
				{showMore ? 'Hide' : 'Show'} details
			</button>
			{showMore && <p>{sculplture.description}</p>}
			<img
				src={sculpture.url}
				alt={sculpture.alt}
			/>
		</>
	)
}

3. Render and Commit

Before the components are displayed on the screen, they must be rendered by React. Understanding the steps in this process will help the developer think about how their code executes and explain its behavior.

Imagine that the components are cooks in the kitchen, assembling tasty dishes from ingredients. In this scenario, React is the waiter who puts in requests from customers and brings them their orders. This process of requesting and serving UI has three steps:

  1. Triggering a render (delivering the diner's order to the kitchen)
  2. Rendering the component (preparing the order in the kitchen)
  3. Committing to the DOM (placing the order on the table)

![[Waiter analogy for how React triggers, renders, and commits.png]]

4. State as a Snapshot

Unlike regular JavaScript variables, React state behaves more like a snapshot. Setting it does not change the state variable the developer already has, but instead triggers a re-render. This can be unintuitive at first!

console.log(count) // 0
setCount(count + 1) // request a re-render with 1
console.log(count) // still 0!

5. Queuing a Series of State updates

Setting state requests a new re-render but does not change the state in the already running code. So score continues to be 0 right after calling setScore(score + 1).

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1)
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1)
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1)

The developer can fix this by passing an updater function when setting state. Replacing setScore(score + 1) with setScore(s => s + 1) will allow the developer to queue multiple state updates.

6. Updating Objects in State

State can hold any kind of JavaScript value, including objects. But the developer should not change objects and arrays that they hold in the React state directly. instead, when the developer wants to update an object or array, they need to create a new one (or making a copy of an existing one), and then update the state to use that copy.

Usually, the developer will use the ... spread syntax to copy objects and arrays that they want to change.

import { useState } from 'react';
 
export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });
 
  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }
 
  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }
 
  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }
 
  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }
 
  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

7. Updating Arrays in State

Arrays are another type of mutable JavaScript objects the developer can store in state and should treat as read-only. Just like with objects, when the developer wants to update an array stored in state, the developer needs to create a new one (or make a copy of an existing one), and then set state to use the new array:

import { useState } from 'react';
 
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];
 
export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );
 
  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }
 
  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}
 
function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}