You want the user to be able to add new colors to a palette. Eventually you will offer a full color picker. For the time being, you want to try adding the color blue to the palette. You start by adding a button to trigger the addition:
function App() {
return (
<div className="App">
{/* initialColors.map(...) */}
<button>Add Blue</button>
</div>
);
}
This button needs a callback. You define this function at the top-level of your script:
function addBlue() {
initialColors.add({hex: '#0000FF', name: 'blue'});
console.log(initialColors);
}
If you were only using plain HTML and JavaScript, you would call document.getElementById
to get a reference to the button and addEventListener
to register the callback. In React, you do not call getElementById
to grab a reference to a component. You already have the component in the JSX. Additionally, you do not call addEventListener
. Instead you assign the event listener as a prop.
In the case of a button, the prop is named onClick
. You wire up the callback directly in the JSX:
function App() {
return (
<div className="App">
{/* initialColors.map(...) */}
<button onClick={addBlue}>Add Blue</button>
</div>
);
}
What happens when you click the button? Do you see a new swatch? What does the console output show?
A blue swatch does not appear because the App
function, which renders the component, has already run. Changing the array does not automatically cause it to re-render. Bummer.
React does provide a mechanism for making a component re-render when the data it relies on changes. You must declare the colors to be part of the component's state by calling React's useState
function. A call to useState
follows this pattern:
const [value, setValue] = useState(initialValue);
The return value is a two-element array, which is destructured into separate variables. You use the first element to read the current value of the state. You use the second element to modify the state, which automatically causes the component to re-render.
To add state to the App
component, you first import the useState
function with this line at the top of your file:
import {useState} from 'react';
You then call useState
to get the current colors and a setter:
function App() {
const [colors, setColors] = useState(initialColors);
// ...
}
Your initialColors
array is used to seed the palette on the first render.
Since the click callback needs to access setColors
, you must define addBlue
inside the component instead of at the top-level. Additionally, it is bad form to modify state. Instead of calling colors.push
, you create a brand new array made of the previous colors and blue using the spread syntax:
function App() {
// ...
const addBlue = () => {
setColors([...colors, {hex: '#0000FF', name: 'blue'}]);
};
// ...
}
Then you adjust the JSX to turn colors
instead of initialColors
into an array of swatches:
function App() {
// ...
return (
<div className="App">
{
colors.map(color =>
<Swatch
key={color.name}
name={color.name}
hex={color.hex}
/>
)
}
<button onClick={addBlue}>Add Blue</button>
</div>
);
}
Try clicking the button. Does the blue swatch appear? What if you click it multiple times? Do multiple blue swatches appear? What do you see in your browser's console?
Try adding this line inside your component:
console.log(new Date());
When you click the button multiple times, how many times does the date appear in your console?
Your component function may run many times over the lifetime of a component. Each time it is called, you execute useState
. Yet this somehow doesn't reset the colors
array. React hangs on to the colors across calls. On the first call, it seeds the array with initialColors
. On subsequent calls, it yields the current colors.
Additionally, on each render, you issue JSX to produce a new chunk of the document. Replacing the DOM is an expensive operation, so this seems like a terrible design decision. React receives your new chunk of document, but it only updates the parts of the document that have been modified. It uses a tree-based difference algorithm to compare your new chunk of DOM to the existing chunk of DOM.