Appearance
Adding Interactivity
FreshSome things on the screen update in response to user input. This section covers how to handle events, update state, and understand React's rendering cycle.
Responding to Events
Add event handlers by passing functions as props:
jsx
export default function Button() {
function handleClick() {
alert('You clicked me!');
}
return <button onClick={handleClick}>Click me</button>;
}Event Handler Conventions
- Define handlers inside the component (they have access to props and state)
- Name them
handle+ event name:handleClick,handleMouseEnter - Pass the function, don't call it:
onClick={handleClick}notonClick={handleClick()}
Inline Event Handlers
jsx
<button onClick={() => alert('clicked')}>Click</button>
<button onClick={function handleClick() { alert('clicked'); }}>Click</button>Event Propagation
Events bubble up through the tree. A click on a child button also fires onClick on the parent div:
jsx
function Toolbar() {
return (
<div onClick={() => alert('toolbar clicked')}>
<button onClick={(e) => {
e.stopPropagation();
alert('button clicked');
}}>
Click me
</button>
</div>
);
}Preventing Default Behavior
jsx
function Signup() {
return (
<form onSubmit={(e) => {
e.preventDefault();
alert('Submitting!');
}}>
<input />
<button>Send</button>
</form>
);
}State: A Component's Memory
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.
jsx
import { useState } from 'react';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
return (
<div>
<button onClick={handleNextClick}>Next</button>
<h2>Sculpture {index + 1}</h2>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>Details here...</p>}
</div>
);
}How useState Works
useState(initialValue)returns[currentValue, setterFunction]- React preserves state between re-renders
- Each render has its own state values (a "snapshot")
- Setting state triggers a re-render
State is Isolated and Private
State is local to a component instance. If you render the same component twice, each copy gets its own state. Changing state in one does not affect the other.
Render and Commit
React renders your UI in three steps:
flowchart LR
A[1. Trigger render] --> B[2. Render component]
B --> C[3. Commit to DOM]- Triggering a render: initial render (
createRoot().render()) or state update (setState) - Rendering the component: React calls your component function. For initial render, React calls the root component. For updates, React calls the component whose state changed.
- Committing to the DOM: React applies the minimal necessary changes to the DOM.
After committing, the browser repaints the screen.
React Only Changes What's Different
React only modifies the DOM nodes if there's a difference between renders. It doesn't touch DOM nodes that haven't changed.
State as a Snapshot
Setting state does not change the variable in your existing code. It requests a re-render with a new value:
jsx
function Counter() {
const [number, setNumber] = useState(0);
return (
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
// This only increments by 1! All three calls see number = 0
}}>+3</button>
);
}Each render's state is fixed. The number variable is always the value from that particular render.
Queueing a Series of State Updates
To update state multiple times before the next render, use an updater function:
jsx
function Counter() {
const [number, setNumber] = useState(0);
return (
<button onClick={() => {
setNumber(n => n + 1); // queue: add 1
setNumber(n => n + 1); // queue: add 1
setNumber(n => n + 1); // queue: add 1
// This increments by 3!
}}>+3</button>
);
}| Queued update | n | Returns |
|---|---|---|
n => n + 1 | 0 | 1 |
n => n + 1 | 1 | 2 |
n => n + 1 | 2 | 3 |
Updating Objects in State
State can hold any JavaScript value, including objects. But don't mutate objects that you hold in state. Instead, create a new object:
jsx
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'barbara@example.com'
});
// WRONG: mutating state directly
person.firstName = 'Marie';
// RIGHT: create a new object
setPerson({
...person,
firstName: 'Marie'
});Updating Nested Objects
jsx
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
}
});
setPerson({
...person,
artwork: {
...person.artwork,
city: 'New Delhi'
}
});Updating Arrays in State
Arrays are mutable in JavaScript, but treat them as immutable when stored in state:
| Operation | Avoid (mutates) | Prefer (returns new array) |
|---|---|---|
| Adding | push, unshift | concat, [...arr] spread |
| Removing | splice, pop, shift | filter, slice |
| Replacing | splice, arr[i] = x | map |
| Sorting | sort, reverse | Copy first, then sort |
jsx
const [artists, setArtists] = useState([]);
// Adding
setArtists([...artists, { id: nextId++, name: name }]);
// Removing
setArtists(artists.filter(a => a.id !== artist.id));
// Replacing
setArtists(artists.map(a => {
if (a.id === artist.id) {
return { ...a, name: newName };
} else {
return a;
}
}));
// Inserting at a position
const insertAt = 1;
const nextArtists = [
...artists.slice(0, insertAt),
{ id: nextId++, name: name },
...artists.slice(insertAt)
];
setArtists(nextArtists);Use Immer for Complex Updates
The Immer library lets you write mutative-looking code that produces immutable updates:
jsx
import { useImmer } from 'use-immer';
const [person, updatePerson] = useImmer(initialPerson);
updatePerson(draft => { draft.artwork.city = 'Lagos'; });