Your first React app
Build a tip calculator in React step by step: split the screen into components, show data, handle a click, and update the screen with state, live as you edit.
- 01Split a screen into componentsLook at a design, name its parts, and write each one as a function that returns markup.
- 02Show data and handle a clickPut values into markup with curly braces, pass them to a component as props, and run a function when a button is clicked.
- 03Update the screen with stateExplain why changing a plain variable doesn't change the screen, and use useState so React shows the new value.
A tip calculator, built in five steps
In this lesson you build a whole React app: a tip calculator. You type the bill, pick a tip, and it shows the tip, the total, and what each person pays. You'll build it in small steps, and each step adds one idea. Every step is live: you see its result, you can click it, and you can change its code.
You start where Setting up a React project left off, with the starter replaced by your own App:
export default function App() { return <h1>Tip calculator</h1>;}Line 1 defines a component called App and exports it, so main.jsx can show it. Line 2 returns what the screen shows: one heading. That's the whole app so far. By the end of step 4, the screen looks like this, and each labelled part is a component of its own:
App draws the whole page, TipButtons draws the row of buttons, and Summary draws the tip and the total.The steps:
- Split the screen into components.
- Show data: work out the tip and total, and put them on the screen.
- Handle a click on a tip button.
- Remember the chosen tip with state, so the screen updates.
- Put it together: type the bill, and split it between people.
Each step previews an idea that a later lesson teaches properly. Here you only need enough to make the app work, and each step says where you'll learn the rest.
In a café, pointing at the menu doesn't make food appear. The waiter writes your choice on the order ticket, and the kitchen makes what the ticket says. If the choice never reaches the ticket, the kitchen never hears about it.
- In real life: The sections of the menu board
- In React: Components: TipButtons, Summary
- In real life: The prices written on the board
- In React: Data shown with curly braces: ${tip.toFixed(2)}
- In real life: A customer pointing at an item
- In React: A click on a button, handled by onClick
- In real life: The waiter writing it on the ticket
- In React: setTipPercent(20): changing state
- In real life: The kitchen making the order from the ticket
- In React: React calling App again and updating the page
A React app is components that turn data into what the screen shows. When something happens, like a click, an event handler changes state, and React calls your components again and updates the page to match.
Step 1: split the screen into components
Look at the picture again and name the parts: a row of tip buttons, and a summary with the tip and total. Each part becomes a component: a function whose name starts with a capital letter and returns markup. For now the numbers are typed in by hand, and the buttons do nothing.
function TipButtons() { return ( <div> <button>10%</button> <button>15%</button> <button>20%</button> </div> );} function Summary() { return ( <div> <p>Tip: <strong>$7.20</strong></p> <p>Total: <strong>$55.20</strong></p> </div> );} export default function App() { return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: $48.00, tip 15%</p> <TipButtons /> <Summary /> </div> );}- Lines 1 to 9 are
TipButtons. It returns a<div>with three buttons. - Lines 11 to 18 are
Summary. It returns the tip and the total, written in by hand. - Lines 20 to 29 are
App, the componentmain.jsxshows. It returns a heading, a line about the bill, and then, on lines 25 and 26,<TipButtons />and<Summary />. Written like tags, they tell React to call those functions and put what they return there.
The result looks finished, but nothing works yet. Press Edit & run and copy line 25 so it appears twice: two rows of buttons show. A component is written once and used as many times as you like.
All three components live in one file for now; step 5 moves two of them into files of their own. Naming components, nesting them, and moving them between files is what Your first component teaches properly. The markup itself is JSX, which Writing markup with JSX covers.
Step 2: show data with curly braces
The numbers in step 1 are typed in by hand, so they're wrong as soon as the bill changes. In step 2, JavaScript works them out from two values, and the components show the results:
const bill = 48;const tipPercent = 15; function TipButtons() { return ( <div> <button>10%</button> <button>15%</button> <button>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons /> <Summary tip={tip} total={total} /> </div> );}- Lines 1 and 2 are the data: a bill of 48 and a tip of 15 percent.
- Lines 24 and 25, inside
App, work out the tip,48 * 15 / 100, which is 7.2, and the total, 55.2. It's plain JavaScript that runs each time React callsApp. - Line 30 puts values into the markup with curly braces:
{tipPercent}shows 15. Inside the braces is JavaScript; the$before{bill.toFixed(2)}is plain text, and toFixed(2) writes the number with two decimals,48.00. - Line 32 passes the tip and total to
Summary:tip={tip}andtotal={total}. Values written on a component's tag like this are its props. - Line 14 receives them. React passes the props as one object, and
{ tip, total }takes the two values out of it with destructuring. Lines 17 and 18 show them:$7.20and$55.20.
App works out tip and total from bill and tipPercent, and passes them to Summary as props. Summary only shows what it's given, with two decimals.Press Edit & run and change the bill on line 1 to 60. The bill line, the tip, and the total all follow: $9.00 and $69.00. You changed the data, and every place that shows it changed with it, the idea from Why React?.
Curly braces get a lesson of their own, JavaScript in JSX with curly braces, and so do props, Passing props to a component.
Step 3: handle a click
Now the buttons need to do something. In React you give a button a function to run when it's clicked, with onClick. A first try changes the tip with an ordinary variable:
const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { let tipPercent = 15; function choose(percent) { tipPercent = percent; console.log("tipPercent is now", tipPercent); } const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={choose} /> <Summary tip={tip} total={total} /> </div> );}- Line 3:
TipButtonsnow takes one prop,onChoose. A prop can be a function, too. - Lines 6 to 8 give each button an
onClick. Its value is an arrow function,() => onChoose(20), which React keeps and calls when you click. The click is an event, and the function is its event handler. - Line 23 makes
tipPercenta plain variable, starting at 15. Lines 25 to 28 definechoose, which sets it and prints it to the console. - Line 37 passes
choosetoTipButtonsasonChoose. So clicking 20% runschoose(20).
Click 20% in the result. The console under it says tipPercent is now 20, but the screen still says tip 15% and $7.20. Click 10%: the console changes, the screen doesn't.
App again, and the page still says tip 15%.Two things go wrong:
- React isn't told. Changing a variable is ordinary JavaScript. Nothing tells React that the screen should change, so it doesn't call
Appagain, and lines 30 to 40 never run with the new value. - The value isn't kept. Even if React did call
Appagain, line 23 would run again and settipPercentback to 15. Each call of a component starts its variables fresh.
Events, handlers, and passing them as props are what Responding to events teaches properly.
Step 4: remember the choice with state
The fix for both problems is state: a value that React remembers for a component between calls, and a function that changes it and tells React to call the component again. You ask for it with useState:
import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}- Line 1 imports
useStatefrom React. Functions whose names start withuseare called hooks. - Line 25 asks React for a piece of state, starting at 15.
useStategives back two things in an array: the current value,tipPercent, and a function that sets a new one,setTipPercent. The brackets take both out with array destructuring. - Line 34 passes
setTipPercenttoTipButtonsasonChoose. So clicking 20% now runssetTipPercent(20). - Everything else is the same as step 2: lines 27 and 28 work out the tip and total, and Summary shows them.
Click 20%. Now the screen says tip 20%, $9.60, and $57.60. Click 10%: $4.80 and $52.80.
setTipPercent(20). React remembers 20 and calls App again. App works out the new tip and total, and React changes only the pieces of text that differ.Here is what happens on that click. setTipPercent(20) stores 20 and tells React this component needs a new render. React calls App again; this time line 25 gives back 20, so lines 27 and 28 work out a tip of 9.6 and a total of 57.6, and the markup describes the new screen. React compares it with the last one and changes only what differs. The playground below runs the same code on this page and shows you exactly what React changed:
Tip calculator
Bill: $48.00, tip 15%
Tip: $7.20
Total: $55.20
nothing yetNo click yet.
Click 10%, 15%, or 20% in the app. Try the same button twice.
Click a tip button in the app. The panel lists every piece of text React changed on the page for that click.
Click 20%: React changes three pieces of text, the 15 in the bill line, the tip, and the total. The heading, the bill, and the buttons stay as they are. Click 20% again: tipPercent is already 20, so nothing on the page changes. How React works: a first look explains this compare-then-change step.
How to read the table: each row asks the same question of step 3's plain variable and step 4's state.
| Question | A plain variable (step 3) | State (step 4) |
|---|---|---|
| Does React remember it between calls of App? | No. Each call runs let tipPercent = 15 again. | Yes. React keeps it and gives it back on the next call. |
| Does changing it make React call App again? | No. React doesn't know it changed. | Yes. setTipPercent(20) asks React for a new render. |
| What does the screen show after a click? | The old value: tip 15%. | The new value: tip 20%, with the new tip and total. |
State is the biggest idea in this lesson, and the course gives it several lessons, starting with State: a component's memory.
Step 5: the finished app
The last step finishes the app. You can type the bill, the chosen tip is marked, and the bill is split between people. The two smaller components move into files of their own, so the example has tabs: App.jsx, TipButtons.jsx, Summary.jsx, and styles.css.
import { useState } from "react";import TipButtons from "./TipButtons.jsx";import Summary from "./Summary.jsx"; export default function App() { const [bill, setBill] = useState("48"); const [tipPercent, setTipPercent] = useState(15); const [people, setPeople] = useState(1); const amount = Number(bill) || 0; const tip = (amount * tipPercent) / 100; const total = amount + tip; return ( <main className="calculator"> <h1>Tip calculator</h1> <label> Bill ($){" "} <input type="number" value={bill} onChange={(event) => setBill(event.target.value)} /> </label> <TipButtons selected={tipPercent} onChoose={setTipPercent} /> <p> People: {people}{" "} <button onClick={() => setPeople(people - 1)} disabled={people === 1}> Remove a person </button> <button onClick={() => setPeople(people + 1)}>Add a person</button> </p> <Summary tip={tip} total={total} people={people} /> </main> );}In App.jsx:
- Lines 2 and 3 import the two components from their files.
- Lines 6 to 8 keep three pieces of state: the bill, the tip percent, and the number of people. The bill starts as the text
"48", because a text box always holds text. Line 10 turns it into a number with Number, and uses 0 while the box is empty. - Lines 19 to 23 are the text box.
value={bill}shows the state, andonChangeruns on every key you type, setting the state to what's in the box. It's a second kind of event; forms get their own lessons, starting with Reacting to input with state. - Line 25 passes
selectedas well, so the buttons know which tip is chosen. Lines 28 and 31 change the number of people, and line 28's button is turned off while there's only one person.
In TipButtons.jsx, line 1 lists the choices and line 6 makes one button per choice with map (Rendering lists explains the key on line 8). Line 9 sets aria-pressed on the chosen button, which tells screen readers it's chosen and lets styles.css outline it. In Summary.jsx, line 6 divides the total by the number of people.
Try it: type 60 as the bill, choose 20%, and press Add a person twice. The tip is $12.00, the total $72.00, and each of the 3 people pays $24.00. Then press Edit & run and make it yours: add a 25% choice to line 1 of TipButtons.jsx, or change the starting bill on line 6 of App.jsx.
The finished app wraps everything in <main> with an <h1>, the right tags for a page's main content and its title. Steps 1 to 4 used a <div> and an <h2>, so the playground could show the step 4 app inside this page, which has its own <main> and title.
The same four steps in another app
Most React features you'll build follow the same four steps. Here is a different one, a tea order where you choose how many cups. Click Add one a few times:
import { useState } from "react"; function Price({ amount }) { return <strong>${amount.toFixed(2)}</strong>;} export default function App() { const [quantity, setQuantity] = useState(1); const price = 3.5; return ( <div> <h2>Green tea</h2> <p> {quantity} × $3.50 = <Price amount={quantity * price} /> </p> <button onClick={() => setQuantity(quantity + 1)}>Add one</button> </div> );}Lines 3 to 5 are a component, Price, that shows an amount with two decimals. Line 8 keeps the quantity in state, starting at 1, and line 9 is the price of one cup. Line 15 shows data with curly braces and passes the line's price to Price as a prop. Line 17 handles a click by setting the state. After two clicks it says 3 × $3.50 = $10.50.
How to read the table: each row is one of the four steps, what it adds, and a line from the tip calculator that does it.
| Step | What it adds | A line that does it |
|---|---|---|
| Components | Name the parts of the screen and write each as a function. | function Summary() { … } |
| Data | Work out values in JavaScript and show them with curly braces, or pass them as props. | <Summary tip={tip} total={total} /> |
| An event | Give a button a function to run when it's clicked. | onClick={() => onChoose(20)} |
| State | Keep a value React remembers, and set it to show a new screen. | const [tipPercent, setTipPercent] = useState(15); |
function Summary({ tip, total }) {<Summary tip={tip} total={total} /><strong>${tip.toFixed(2)}</strong>const tip = (bill * tipPercent) / 100;onClick={() => onChoose(20)}onChange={(event) => setBill(event.target.value)}const [tipPercent, setTipPercent] = useState(15);<TipButtons onChoose={setTipPercent} />
Each card is a line from the tip calculator. Sort it by what it does: a component, showing data, handling an event, or state.
Common mix-ups
- "Changing a variable updates the screen." No. React only calls your component again when state changes. Step 3's variable changed, and the screen didn't.
- "
onClick={choose(20)}runs choose when I click." No. It callschoose(20)right away, while React draws the button. GiveonClicka function:onClick={() => choose(20)}. - "After
setTipPercent(20),tipPercentis 20 on the next line." No. The set function asks for a new render.tipPercentis 20 in the next call ofApp, not in the one that's running. State as a snapshot explains why. - "React redraws the whole page after a click." No. It calls your components again, compares, and changes only what differs: three pieces of text in the playground.
- "Each component needs its own file." No. Steps 1 to 4 kept all three in one file. Separate files help once a component is used in many places or grows long.
- "I can call
useStateanywhere, like inside the click handler." No. Call hooks at the top of your component, as line 25 does in step 4. The rules of hooks explains why.
Practice exercises
Each exercise works on the calculator, or a small piece of it. Press Edit & run to change the code, and use the result to check your answer.
This is step 2 with a different bill and tip, on lines 1 and 2. What total does the page show? Work it out first.
const bill = 40;
const tipPercent = 20;
function TipButtons() {
return (
<div>
<button>10%</button>
<button>15%</button>
<button>20%</button>
</div>
);
}
function Summary({ tip, total }) {
return (
<div>
<p>Tip: <strong>${tip.toFixed(2)}</strong></p>
<p>Total: <strong>${total.toFixed(2)}</strong></p>
</div>
);
}
export default function App() {
const tip = (bill * tipPercent) / 100;
const total = bill + tip;
return (
<div className="calculator">
<h2>Tip calculator</h2>
<p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p>
<TipButtons />
<Summary tip={tip} total={total} />
</div>
);
}const bill = 40;const tipPercent = 20; function TipButtons() { return ( <div> <button>10%</button> <button>15%</button> <button>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons /> <Summary tip={tip} total={total} /> </div> );}Line 24 works out the tip, 40 × 20 / 100 = 8, and line 25 adds it to the bill: 48. Line 18 shows it with two decimals: $48.00.
Make the calculator start at 20% instead of 15%. Before you click anything, what does the Tip line show?
import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(20); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}The value you give useState is used only the first time React calls App. With 20, the tip starts at 48 × 20 / 100 = 9.6, shown as $9.60.
Add a fourth tip button, 25%. After you click it, what does the Tip line show?
import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> <button onClick={() => onChoose(25)}>25%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}The new button calls onChoose(25), which is setTipPercent(25). React calls App again with 25: the tip is 48 × 25 / 100 = 12, shown as $12.00. Nothing else needed changing, because the tip and total are worked out from the state.
This counter breaks as soon as it starts. Press Edit & run: the result shows an error instead of the app. Read it, find the mistake on line 9, and fix it. Then click Add a person twice. What does the page say?
import { useState } from "react";
export default function App() {
const [people, setPeople] = useState(1);
return (
<p>
People: {people}{" "}
<button onClick={setPeople(people + 1)}>Add a person</button>
</p>
);
}import { useState } from "react"; export default function App() { const [people, setPeople] = useState(1); return ( <p> People: {people}{" "} <button onClick={() => setPeople(people + 1)}>Add a person</button> </p> );}onClick={setPeople(people + 1)} called setPeople while App was rendering, which asked for another render, which called it again, and React stopped with "Too many re-renders. React limits the number of renders to prevent an infinite loop." With an arrow function, setPeople runs only on a click: 1, then 2, then 3.
Add an Add a person button and an "Each person pays" line under the total. At 15% with 3 people, what does each person pay?
import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total, people }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> <p>Each person pays: <strong>${(total / people).toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const [people, setPeople] = useState(1); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <p>People: {people}</p> <button onClick={() => setPeople(people + 1)}>Add a person</button> <Summary tip={tip} total={total} people={people} /> </div> );}A component can have as many pieces of state as it needs. The total at 15% is 55.2, and Summary divides it by people: 55.2 / 3 = 18.4, shown as $18.40.
Turn off the button for the tip that's already chosen, so you can see which one it is. After you click 10%, which button is turned off?
import { useState } from "react"; const bill = 48; function TipButtons({ onChoose }) { return ( <div> <button onClick={() => onChoose(10)}>10%</button> <button onClick={() => onChoose(15)}>15%</button> <button onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}import { useState } from "react"; const bill = 48; function TipButtons({ selected, onChoose }) { return ( <div> <button disabled={selected === 10} onClick={() => onChoose(10)}>10%</button> <button disabled={selected === 15} onClick={() => onChoose(15)}>15%</button> <button disabled={selected === 20} onClick={() => onChoose(20)}>20%</button> </div> );} function Summary({ tip, total }) { return ( <div> <p>Tip: <strong>${tip.toFixed(2)}</strong></p> <p>Total: <strong>${total.toFixed(2)}</strong></p> </div> );} export default function App() { const [tipPercent, setTipPercent] = useState(15); const tip = (bill * tipPercent) / 100; const total = bill + tip; return ( <div className="calculator"> <h2>Tip calculator</h2> <p>Bill: ${bill.toFixed(2)}, tip {tipPercent}%</p> <TipButtons selected={tipPercent} onChoose={setTipPercent} /> <Summary tip={tip} total={total} /> </div> );}State lives in App, and TipButtons gets it as a prop. Each button compares its own number with selected, so after a click on 10%, React calls App and TipButtons again, and the 10% button is the one that's turned off.
Check your understanding
For each code question, ask: is this value state or a plain variable, and does the click tell React? Press Edit & run on a question's code to check.
Question 1 of 7You click Add a person twice. What does this page show?
Read the code, then predictexport default function App() { let people = 1; function addPerson() { people = people + 1; } return ( <p> People: {people} <button onClick={addPerson}>Add a person</button> </p> ); }Choose an answer to see the explanation.
Question 2 of 7This version keeps
peoplein state. What does it show after two clicks?Read the code, then predictimport { useState } from "react"; export default function App() { const [people, setPeople] = useState(1); return ( <p> People: {people} <button onClick={() => setPeople(people + 1)}>Add a person</button> </p> ); }Choose an answer to see the explanation.
Question 3 of 7Which
onClickgives the button a function to run when it's clicked?Choose an answer to see the explanation.
Question 4 of 7What does
useState(15)give back?Choose an answer to see the explanation.
Question 5 of 7A click calls
setTipPercent(20). What does React do next?Choose an answer to see the explanation.
Question 6 of 7What does this page show?
Read the code, then predictfunction Summary({ tip, total }) { return ( <p> Tip ${tip.toFixed(2)}, total ${total.toFixed(2)} </p> ); } export default function App() { return <Summary tip={6} total={46} />; }Choose an answer to see the explanation.
Question 7 of 7Why split the calculator into TipButtons and Summary instead of one big App?
Choose an answer to see the explanation.
Key takeaways
- Start from the screen: name its parts, and write each as a component, a capitalized function that returns markup.
- Work out values with plain JavaScript, show them with curly braces, and pass them to other components as props.
- Give a button a function to run later with
onClick={() => …}. WritingonClick={f()}calls it right away. - Changing a plain variable doesn't change the screen: React isn't told, and the next call starts it fresh.
useStategives a value React remembers and a set function. Setting it makes React call the component again and change only what differs on the page.
Remember the one-liner.
Components show data; events set state; React calls the components again and updates the page.
Coming next: Your first component. It starts the Describing the UI stage and teaches components properly: writing them, nesting them, and moving them into their own files.