cf.completefrontend
Open lab My progress
THE REACT FIELD GUIDE

Keeping components pure

Learn why React components should only calculate while rendering, watch a shared variable break a cart, and see how StrictMode's double render catches it.

By the end, you can
  • 01
    Tell a pure component from an impure oneCheck that a component only reads its props and returns the same JSX for them, and spot a line that changes something outside it.
  • 02
    Read StrictMode's double renderExplain why React calls your components twice in development, which result it keeps, and what the console shows for it.
  • 03
    Put changes where they belongMove side effects into event handlers or Effects, and know that changing an array or variable you made during the same render is fine.

A numbered cart that counts wrong

A checkout page often numbers the lines in your cart: 1, 2, 3. Here is a first try. Each CartLine adds 1 to a counter and shows the new number. Read it, and guess what the result will show before you look:

A numbered cart (with a bug on purpose)Pop out in the code editor (opens in a new tab)JSX
let lineNumber = 0; function CartLine({ name }) {  lineNumber = lineNumber + 1;  return <li>{lineNumber}. {name}</li>;} export default function App() {  return (    <ul>      <CartLine name="Tea" />      <CartLine name="Cake" />      <CartLine name="Lemonade" />    </ul>  );}
ResultLoading…

Line 1 makes a variable, lineNumber, outside every component. Line 4 adds 1 to it each time React calls CartLine, and line 5 shows it next to the name. App uses CartLine three times, on lines 11 to 13. You probably expected 1, 2, 3. The result shows 2. Tea, 4. Cake, and 6. Lemonade.

Nothing is wrong with React. Every live example on this page runs inside <StrictMode>, like a new project does (you met it in Setting up a React project). In development, StrictMode calls each component twice on purpose. Each call of CartLine adds 1 to the same lineNumber, so the numbers jump by two. StrictMode didn't make the bug; it made you see it. The bug is line 4: while React renders CartLine, it changes something that existed before the call.

Here is the cart written so that every call only reads its own inputs:

The numbered cart, fixedPop out in the code editor (opens in a new tab)JSX
function CartLine({ number, name }) {  return <li>{number}. {name}</li>;} export default function App() {  return (    <ul>      <CartLine number={1} name="Tea" />      <CartLine number={2} name="Cake" />      <CartLine number={3} name="Lemonade" />    </ul>  );}
ResultLoading…

Line 1 takes the number as a prop, beside the name, and line 2 shows it. Lines 8 to 10 give each line its number. There is no variable outside the component any more, so it doesn't matter how many times React calls CartLine: the result is always 1, 2, 3. Components like this one are called pure.

Real-life analogyA calculator and a tally counter

A shop assistant adds up a bill on a calculator, and a doorman clicks a tally counter for every customer who comes in. Ask the assistant to work out the bill again and you get the same total. Click the tally counter again and the count goes up, whether anyone came in or not.

In real life: A calculator: 3 × 2 shows 6, every time you work it out
In React: A pure component: the same props give the same JSX
In real life: A tally counter at a shop door: each press adds 1
In React: The first CartLine: each call adds 1 to lineNumber
In real life: Someone presses the tally counter twice by mistake, and the count is wrong
In React: StrictMode calls CartLine twice, and the numbers jump
In real life: Working out 3 × 2 twice changes nothing
In React: Calling a pure component twice changes nothing
The main idea

A React component should be pure. While React renders it, it only calculates: it reads its props (and later, its state) and returns JSX. It changes nothing that existed before the call, and the same inputs always give the same JSX. React may call it any number of times, so anything else is a bug waiting to show.

What makes a function pure

Purity is a JavaScript idea before it's a React one (the Pure functions lesson covers it in full). A function is pure when two things are true:

  • It minds its own business. It changes no variable or object that existed before it was called.
  • The same inputs give the same output. Call it with the same arguments, and it returns the same result, every time.

Here are two functions for a cart. One is pure and one isn't. Press Edit & run to see what they print:

A pure function and an impure onePop out in the code editor (opens in a new tab)JavaScript
function lineTotal(price, quantity) {
  return price * quantity;
}

let cartTotal = 0;

function addToCart(price, quantity) {
  cartTotal = cartTotal + price * quantity;
  return cartTotal;
}

console.log(lineTotal(3, 2));
console.log(lineTotal(3, 2));
console.log(addToCart(3, 2));
console.log(addToCart(3, 2));

lineTotal (lines 1 to 3) only multiplies its two arguments, so lines 12 and 13 both print 6. addToCart (lines 7 to 10) also changes cartTotal, a variable made on line 5, outside the function. That's called a side effect: anything a function does besides working out its return value. Because of it, the same call gives 6 on line 14 and 12 on line 15. You can't tell what addToCart(3, 2) returns without knowing what ran before it.

A component is a function too: its inputs are its props, and its output is JSX. React assumes every component is pure, like a formula. This one is:

CartLine as a formulaPop out in the code editor (opens in a new tab)JSX
function CartLine({ name, price, quantity }) {  const total = price * quantity;  return (    <li>      {name} × {quantity}: ${total}    </li>  );} export default function App() {  return (    <ul>      <CartLine name="Tea" price={3} quantity={2} />      <CartLine name="Cake" price={4} quantity={1} />      <CartLine name="Tea" price={3} quantity={2} />    </ul>  );}
ResultLoading…

Line 1 takes three props. Line 2 makes a new variable, total, from them, and line 5 shows the line. Lines 13 and 15 pass exactly the same props, so they show exactly the same line, Tea × 2: $6. Making total is fine: it's a new variable, made during this call.

Three rows. Each row: the props a CartLine gets, an arrow into the function card CartLine, and an arrow to the line it returns. Row 1: Tea, price 3, times 2 gives Tea × 2: $6. Row 2: Cake, price 4, times 1 gives Cake × 1: $4. Row 3 has the same props as row 1 and gives the same line.props inthe componentwhat it returnsTea, price 3, × 2CartLineTea × 2: $6Cake, price 4, × 1CartLineCake × 1: $4Tea, price 3, × 2CartLineTea × 2: $6
Lines 13 and 15 give CartLine the same props, so they get the same line back, however many times React calls it. Different props, like Cake's on line 14, give a different line.

Side effects aren't only changed variables. Each of these makes a component impure if it runs while rendering:

  • changing a variable or object from outside the component, including its props;
  • changing the page directly, like setting document.title;
  • sending a request with fetch, or saving to localStorage;
  • reading something that differs from call to call, like Math.random() or the time from new Date(): it breaks "same inputs, same output".

What goes wrong when rendering changes a variable

You might think: "StrictMode is only in development. My users would see 1, 2, 3." They would, at first. But React renders a component again whenever its data changes. Here is the numbered cart with one button that changes something unrelated: a gift wrap choice. Press Gift wrap: off a few times:

The numbered cart, rendered again by a clickPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; let lineNumber = 0; function CartLine({ name }) {  lineNumber = lineNumber + 1;  return <li>{lineNumber}. {name}</li>;} export default function App() {  const [wrapped, setWrapped] = useState(false);  return (    <>      <ul>        <CartLine name="Tea" />        <CartLine name="Cake" />        <CartLine name="Lemonade" />      </ul>      <button onClick={() => setWrapped(!wrapped)}>        Gift wrap: {wrapped ? "on" : "off"}      </button>    </>  );}
ResultLoading…

Lines 3 to 8 are the same impure CartLine as before. Line 11 keeps the gift wrap choice in state, and the button on lines 19 to 21 flips it. A state change makes React render App again, and every CartLine inside it, as How React works: a first look showed. Each render calls each CartLine again, and each call adds to lineNumber, which nothing ever resets. After one click the cart says 8, 10, 12. After two: 14, 16, 18.

Left, a window named calls lists six calls in order: Tea call 1 gets 1, Tea call 2 gets 2, Cake call 1 gets 3, Cake call 2 gets 4, Lemonade call 1 gets 5, Lemonade call 2 gets 6. The first calls are faded. Below the middle, a box named lineNumber holds 6. An arrow labelled second calls kept leads to a page window showing 2. Tea, 4. Cake, 6. Lemonade.callsTea, call 1: 1Tea, call 2: 2Cake, call 1: 3Cake, call 2: 4Lemonade, call 1: 5Lemonade, call 2: 6second calls keptpage2. Tea4. Cake6. Lemonade6lineNumberone variable, outsideevery call of CartLine
With StrictMode, React calls each CartLine twice in a row, and every call adds 1 to the same lineNumber. React keeps each second call, the ones not faded, so the page shows 2, 4, and 6.

The playground replays these calls one by one. Press Click Gift wrap to render again, and watch the list of calls. Then turn StrictMode off: the first render shows 1, 2, 3, the way a production build would, and the next render shows 4, 5, 6. Last, choose Pure CartLine: 1, 2, 3, whatever you do.

Playground: watch React call CartLine
The pagewith StrictMode
  • 2. Tea
  • 4. Cake
  • 6. Lemonade

Gift wrap: off

lineNumber is now 6.

Calls in this render6 calls
  1. CartLine Tea, call 1shows 1. Thrown away.
  2. CartLine Tea, call 2shows 2. React keeps this one.
  3. CartLine Cake, call 1shows 3. Thrown away.
  4. CartLine Cake, call 2shows 4. React keeps this one.
  5. CartLine Lemonade, call 1shows 5. Thrown away.
  6. CartLine Lemonade, call 2shows 6. React keeps this one.
Try it yourself

This is the first render. StrictMode called each CartLine twice, so lineNumber went up 6 times, to 6. React kept each second call, so the page shows 2, 4, 6.

This replays the calls React makes for the gift-wrap cart above: each CartLine in order, and with StrictMode on, twice in a row, keeping the second result. It isn't React itself; the live examples are, and this lesson's test checks every page shown here against them. Changing a choice starts again from a fresh page. Reset goes back to the impure CartLine with StrictMode.

Without StrictMode, the first render looks right, and the bug only shows when something else makes the cart render again. That might be a click far away, a timer, or new data from a server. Bugs like that are hard to find, because the component looks fine and the trigger is somewhere else.

There's a second problem: order. The numbers only came out in order because React happened to render Tea, then Cake, then Lemonade. A component should never depend on which other components rendered before it. React may skip a component whose inputs didn't change, or start a render and throw it away. A pure component gives the right answer in any order, any number of times.

How StrictMode's double render shows the bug

StrictMode is a component from react that turns on extra checks, in development only, for everything inside it. The most important check: React calls each of your components twice in a row and keeps the second result. A pure component returns the same JSX both times, so you see no difference. An impure one changes something on the first call, and the second call shows it.

Top row, in development with StrictMode: CartLine call 1, then call 2, then React keeps call 2's result, then commits it to the page. Bottom row, in a production build: call 1, then React keeps it, then commits.In development, with StrictModecall 1call 2keep call 2commitresult thrown awayIn a production buildcall 1keep call 1commit
In development, StrictMode calls your component a second time straight after the first and keeps the second result. A production build, the one your users load, calls it once. A pure component returns the same thing either way.

A console.log in the component shows both calls. This pure CartLine logs each time React calls it. The console under the result shows what it printed:

Logging each call of a pure CartLinePop out in the code editor (opens in a new tab)JSX
function CartLine({ name }) {  console.log("Rendering", name);  return <li>{name}</li>;} export default function App() {  return (    <ul>      <CartLine name="Tea" />      <CartLine name="Cake" />    </ul>  );}
ResultLoading…

Line 2 logs the name. The console shows Rendering Tea and Rendering Cake, each with a small 2 beside it: when the same line is printed twice in a row, this page's console shows it once, with a count. Both calls printed the same thing, which is what a pure component does. Now the impure one:

Logging each call of the impure CartLinePop out in the code editor (opens in a new tab)JSX
let lineNumber = 0; function CartLine({ name }) {  lineNumber = lineNumber + 1;  console.log(name, "got number", lineNumber);  return <li>{lineNumber}. {name}</li>;} export default function App() {  return (    <ul>      <CartLine name="Tea" />      <CartLine name="Cake" />    </ul>  );}
ResultLoading…

Line 5 logs the number each call takes. This time the console shows four different lines: Tea got number 1, Tea got number 2, Cake got number 3, Cake got number 4. Two calls of the same component with the same props disagree: that's the sign of impure rendering. The page shows 2 and 4, the second calls.

What the console looks like where you work

This page's previews show both calls' logs in the same way. In your own project, in a browser with the React DevTools extension installed, logs from the second call appear slightly dimmed, so you can tell the two calls apart. React tells DevTools when its second call starts and ends, and DevTools does the dimming. In the DevTools settings, under Debugging, you can turn on "Hide logs during additional invocations in Strict Mode" to hide them. React itself stopped hiding them in React 18.

In a production build, the one npm run build makes for your users, StrictMode does nothing, and React calls each component once. This preview has no production build, but it can leave StrictMode out. Its main.jsx tab starts the gift wrap cart from the last section without it:

The gift wrap cart without StrictModePop out in the code editor (opens in a new tab)JSX
import { createRoot } from "react-dom/client";import App from "./App.jsx"; createRoot(document.getElementById("root")).render(<App />);
ResultLoading…

Line 4 renders <App /> with no <StrictMode> around it. The cart shows 1, 2, 3. Press Gift wrap: off and it shows 4, 5, 6: the same bug, found later.

Your component's body isn't the only thing StrictMode repeats. Each row is one thing React does twice in development, and what that finds:

What StrictMode repeats in development
WhatWhat React doesWhat it finds
Your component's bodyReact calls it twice in a row and keeps the second result.Rendering that changes something outside the component.
Functions you pass to useState, useReducer, useMemo, and to set functionsReact calls them twice.Calculations that aren't pure.
EffectsWhen the component first appears, React runs the Effect, cleans it up, and runs it again.Effects that don't clean up after themselves.
Ref callbacksWhen the component first appears, React runs them, cleans up, and runs them again. React 19 added this.Refs that don't clean up after themselves.
Event handlersReact runs them once, as usual.Nothing: handlers are allowed to change things.

React 19 changed two details. During the second call of a component, useMemo and useCallback reuse the results from the first call, and ref callbacks now run twice when a component first appears. Effects, refs and useMemo come later in the course. For now, remember: StrictMode never changes what a pure app shows. It only makes impure code show itself sooner. Keep it on.

Read it in React's own code

The second call is in renderWithHooks, the function that calls your component, in ReactFiberHooks.js, line 616, at React 19.3.1. You don't need to read it to use React; How hooks work inside, in the course's last stage, does.

The second call, from ReactFiberHooks.js (React 19.3.1, lines 616 to 628)JavaScript
  if (shouldDoubleRenderDEV) {
    // In development, components are invoked twice to help detect side effects.
    setIsStrictModeForDevtools(true);
    try {
      children = renderWithHooksAgain(
        workInProgress,
        Component,
        props,
        secondArg,
      );
    } finally {
      setIsStrictModeForDevtools(false);
    }

By this point React has called Component, your function, once. Line 1 checks that StrictMode is on in a development build. Line 3 tells DevTools the second call is starting (that's what dims its logs), and lines 5 to 10 call your component again with the same props. Its result replaces children, the first call's result. Line 12 tells DevTools the second call is over.

Where changes belong: event handlers and Effects

Apps have to change things: add an order, save a cart, send a message. Purity doesn't forbid that; it says when. Not while React is rendering. The first place for a side effect is an event handler: a function React runs when something happens, like a click. Handlers are written inside your component, but they don't run while it renders, so they don't need to be pure. (The Introduction to events lesson covers events in plain JavaScript.)

Numbering orders in a click handlerPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; export default function App() {  const [orders, setOrders] = useState([]);   function placeOrder(drink) {    const number = orders.length + 1;    setOrders([...orders, `#${number} ${drink}`]);    console.log("Order placed:", number);  }   return (    <>      <button onClick={() => placeOrder("Tea")}>Order tea</button>      <button onClick={() => placeOrder("Cake")}>Order cake</button>      <ul>        {orders.map((order) => (          <li key={order}>{order}</li>        ))}      </ul>    </>  );}
ResultLoading…

Line 4 keeps the orders in state. placeOrder (lines 6 to 10) is the click handler: line 7 works out the next number, line 8 gives React a new list with the order added, and line 9 logs it. Click Order tea, then Order cake: the list shows #1 Tea and #2 Cake, and the console shows Order placed: 1 and Order placed: 2, once each. StrictMode doesn't call event handlers twice. The numbers come from orders, the component's state, not from a variable outside it, and React renders the list from that state.

Some side effects aren't caused by any event. Say the browser tab's title should show how many items are in the cart, whatever changed the count. For that, React has Effects: code that runs after React has put a render on the page. Use them as a last resort, when no event handler fits.

Keeping the tab title in step, in an EffectPop out in the code editor (opens in a new tab)JSX
import { useEffect, useState } from "react"; export default function App() {  const [count, setCount] = useState(0);   useEffect(() => {    document.title = `Cart (${count})`;    console.log("Saved count:", count);  });   return <button onClick={() => setCount(count + 1)}>Items: {count}</button>;}
ResultLoading…

Lines 6 to 9 pass a function to useEffect. React runs it after each render reaches the page: line 7 sets the tab's title, and line 8 logs. When the result first appears, the console shows Saved count: 0 twice. That's StrictMode's check for Effects: when a component first appears, React runs its Effect, cleans it up, and runs it again, in development only. Click the button and you get one Saved count: 1 per click. The Synchronizing with effects lesson teaches Effects properly, including the clean up.

How to read the table: each row is a place code can sit in a component, when it runs, and whether it may change things.

Where your code runs
Where the code isWhen it runsCan it change things?
The component's body (rendering)Every time React renders the component, and twice in development with StrictMode.No. It only works out what to show.
An event handler, like onClickOnce each time the event happens, like a click.Yes. This is the first place to put side effects.
An Effect, from useEffectAfter React has put a render on the page.Yes, as a last resort, to keep something outside React in step.

Why does React care so much? Because purity lets React do its job well:

Changing what you just made is fine

Purity is about things that existed before the call. A component can make its own arrays and variables while it renders, and change them as much as it likes. That's called local mutation (mutation means changing a value in place). Nobody outside the call can see it:

A receipt built with push and a running totalPop out in the code editor (opens in a new tab)JSX
const items = [  { name: "Tea", price: 3 },  { name: "Cake", price: 4 },  { name: "Lemonade", price: 2 },]; export default function Receipt() {  const lines = [];  let total = 0;  for (const item of items) {    total = total + item.price;    lines.push(      <li key={item.name}>        {item.name}: ${item.price} (so far ${total})      </li>    );  }  return <ul>{lines}</ul>;}
ResultLoading…

Line 8 makes a new, empty array, and line 9 a new total of 0. The loop on lines 10 to 17 changes both: line 11 adds to total, and line 12 pushes a line into lines. Line 18 returns them. The result shows three lines, ending Lemonade: $2 (so far $9). StrictMode called Receipt twice, and each call started again from an empty array and 0, so both calls returned the same receipt. Compare that with the numbered cart: there, lineNumber was made once, on line 1, outside every call.

The thing to watch is an array or object you were given. Props are shared: the parent, and maybe other components, hold the very same array (the Objects and references lesson explains why). Some array methods change the array itself. sort is one:

Sorting the menu a component was given (with a bug on purpose)Pop out in the code editor (opens in a new tab)JSX
const menu = [  { name: "Tea", price: 3 },  { name: "Cake", price: 4 },  { name: "Lemonade", price: 2 },]; function CheapestFirst({ items }) {  items.sort((a, b) => a.price - b.price);  return <p>Cheapest first: {items.map((item) => item.name).join(", ")}</p>;} function Menu({ items }) {  return <p>Menu: {items.map((item) => item.name).join(", ")}</p>;} export default function App() {  return (    <>      <Menu items={menu} />      <CheapestFirst items={menu} />      <Menu items={menu} />    </>  );}
ResultLoading…

Lines 1 to 5 make one menu, and App passes it to three components on lines 19 to 21. Line 8 sorts items, cheapest first. But items is menu: the same array. The first Menu rendered before the sort and shows Tea, Cake, Lemonade. The second rendered after it and shows Lemonade, Tea, Cake. Same component, same props, different result: the order of rendering decided it.

Left, shared: a crate named menu, made before rendering, now holds Lemonade, Tea, Cake. An arrow from the function card CheapestFirst points into it: it sorted the shared menu, so Menu sees the new order too. Right, local: a crate named sorted, made during this call, holds Lemonade, Tea, Cake. An arrow from CheapestFirst points into it, and the menu stays as it was.Shared: made before renderingLemonadeTeaCakemenuCheapestFirstMenu sees the new order tooLocal: made during this callLemonadeTeaCakesortedCheapestFirstmenu stays as it was
Left: items.sort() sorts the shared menu itself, so the second Menu shows the new order. Right: [...items] makes a new array during this call, and sorting it changes nothing anyone else can see.

The fix is to sort a copy that you make during this call:

Sorting a copy insteadPop out in the code editor (opens in a new tab)JSX
const menu = [  { name: "Tea", price: 3 },  { name: "Cake", price: 4 },  { name: "Lemonade", price: 2 },]; function CheapestFirst({ items }) {  const sorted = [...items].sort((a, b) => a.price - b.price);  return <p>Cheapest first: {sorted.map((item) => item.name).join(", ")}</p>;} function Menu({ items }) {  return <p>Menu: {items.map((item) => item.name).join(", ")}</p>;} export default function App() {  return (    <>      <Menu items={menu} />      <CheapestFirst items={menu} />      <Menu items={menu} />    </>  );}
ResultLoading…

Line 8 makes a new array with spread, [...items], and sorts that. Sorting the copy is local mutation, so it's fine. Line 9 shows the copy. Both Menu lines now show Tea, Cake, Lemonade. Arrays also have toSorted(), which returns a sorted copy in one step: items.toSorted((a, b) => a.price - b.price). The Sorting arrays and Immutability lessons list which methods change the array and which make a copy.

A checkout summary that stays pure

Here is everything together in a checkout page: lines sorted by cost, a subtotal, a discount, and a button that places orders. Click Place order twice:

import { useState } from "react"; const cart = [  { name: "Cake", price: 4, quantity: 1 },  { name: "Tea", price: 3, quantity: 2 },  { name: "Lemonade", price: 2, quantity: 4 },]; function Summary({ items, discount }) {  const sorted = [...items].sort((a, b) => b.price * b.quantity - a.price * a.quantity);  let subtotal = 0;  for (const item of sorted) {    subtotal = subtotal + item.price * item.quantity;  }  const total = subtotal - discount;  return (    <>      <ol>        {sorted.map((item) => (          <li key={item.name}>            {item.name} × {item.quantity}: ${item.price * item.quantity}          </li>        ))}      </ol>      <p>Total: ${total}</p>    </>  );} export default function App() {  const [orders, setOrders] = useState([]);   function placeOrder() {    setOrders([...orders, orders.length + 1]);  }   return (    <>      <Summary items={cart} discount={2} />      <button onClick={placeOrder}>Place order</button>      <p>Orders placed: {orders.length ? orders.map((n) => `#${n}`).join(", ") : "none"}</p>    </>  );}
ResultLoading…

Summary (lines 9 to 28) only calculates. Line 10 sorts a copy of items, most expensive line first. Lines 11 to 14 add up a subtotal in a local variable, and line 15 takes off the discount. The result lists Lemonade × 4: $8, Tea × 2: $6, and Cake × 1: $4, then Total: $16. The one change to the app's data happens in placeOrder (lines 33 to 35), a click handler, which gives React a new list of orders. After two clicks the page says Orders placed: #1, #2. Render it as often as you like: the summary stays the same, and only clicks add orders.

Fine while rendering, or a side effect?
  • lineNumber = lineNumber + 1;
  • const total = price * quantity;
  • items.sort(byPrice);
  • const sorted = [...items].sort(byPrice);
  • document.title = `Cart (${count})`;
  • lines.push(<li key={name}>{name}</li>);
  • const label = name.toUpperCase();
  • fetch("/api/orders", { method: "POST" });
Try it yourself
0 of 8 correct

Each line sits in a component's body, so it runs while React renders. There, items comes from the props, lines was made earlier in the same render, and lineNumber lives outside the component. Is the line fine there, or a side effect that belongs somewhere else?

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "StrictMode causes the bug." No. The bug is the impure code. StrictMode makes it show on the first render instead of a later one. Without it, the gift wrap cart went wrong on the first click.
  • "My users see everything twice." No. The double call happens only in development. A production build calls each component once.
  • "A pure component can't change any variable." No. It can make and change its own variables and arrays during the render, like total and lines in the receipt.
  • "Event handlers must be pure too." No. They run when something happens, not while rendering. They are the first place for side effects.
  • "Sorting a prop is fine, it's only reading it." No. sort, reverse, and push change the array itself, and props are shared with the parent. Sort a copy.
  • "Pure components render less often." No. Purity doesn't change when React renders. It makes rendering safe to repeat, skip, or throw away, which is what React's performance tools rely on.

Practice exercises

Every result here runs in StrictMode, like the examples above. Make a guess first, then use the hints. Press Edit & run to change the code and watch the result.

Exercise 1 · Warm-upPredict the visit number

What does the heading say in development, with StrictMode? Answer first, then press Edit & run to check.

let visits = 10;

export default function Welcome() {
  visits = visits + 1;
  return <h1>Welcome back! Visit {visits}</h1>;
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the impure line

    The heading looks right. Which line makes this component impure?

    let lastShown = ""; export default function ProductTitle({ name = "Tea", price = 3 }) {  const label = name.toUpperCase();  const withPrice = `${label} ($${price})`;  lastShown = withPrice;  return <h2>{withPrice}</h2>;}
    ResultLoading…

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeNumber the lines from the list

      The cart shows 2, 4, 6. Fix it so the numbers come from each name's place in cart, not from a shared counter. What does the third line show?

      const cart = ["Tea", "Cake", "Lemonade"];let count = 0; function CartLine({ name }) {  count = count + 1;  return <li>{count}. {name}</li>;} export default function App() {  return (    <ul>      {cart.map((name) => (        <CartLine key={name} name={name} />      ))}    </ul>  );}
      ResultLoading…

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeSort a copy, not the props

        The second Menu shows the cheapest-first order. Fix CheapestFirst so it doesn't change the menu. What does the second Menu line show then?

        const menu = [  { name: "Tea", price: 3 },  { name: "Cake", price: 4 },  { name: "Lemonade", price: 2 },]; function CheapestFirst({ items }) {  items.sort((a, b) => a.price - b.price);  return <p>Cheapest first: {items.map((item) => item.name).join(", ")}</p>;} function Menu({ items }) {  return <p>Menu: {items.map((item) => item.name).join(", ")}</p>;} export default function App() {  return (    <>      <Menu items={menu} />      <CheapestFirst items={menu} />      <Menu items={menu} />    </>  );}
        ResultLoading…

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeGive out tickets on click

          Click Place order twice: the tickets read 2, 4. Move the numbering out of rendering, into the click. What does the page say after two clicks?

          import { useState } from "react"; let nextTicket = 1; export default function App() {  const [orders, setOrders] = useState([]);  const ticket = nextTicket;  nextTicket = nextTicket + 1;   return (    <>      <button onClick={() => setOrders([...orders, ticket])}>Place order</button>      <p>Tickets: {orders.join(", ")}</p>    </>  );}
          ResultLoading…

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeBuild the receipt in a loop

            The receipt is written by hand and shows only Tea. Build one line per item with a loop, a local array, and a running total. What does the last line show?

            const items = [  { name: "Tea", price: 3 },  { name: "Cake", price: 4 },  { name: "Lemonade", price: 2 },]; export default function Receipt() {  return (    <ul>      <li>Tea: $3 (total $3)</li>    </ul>  );}
            ResultLoading…

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each code question, ask: does anything the component changes exist before the call? And remember that StrictMode calls it twice. Press Edit & run on a question's code to check your answer.

              The keeping components pure quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What makes a React component pure?

                Choose an answer to see the explanation.

              2. Question 2 of 7With StrictMode on, in development, what does this guest list show?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                let seat = 0;
                
                function Guest({ name }) {
                  seat = seat + 1;
                  return <li>Seat {seat}: {name}</li>;
                }
                
                export default function App() {
                  return (
                    <ul>
                      <Guest name="Asha" />
                      <Guest name="Ben" />
                    </ul>
                  );
                }

                Choose an answer to see the explanation.

              3. Question 3 of 7A production build shows the guest list as Seat 1: Asha, Seat 2: Ben. What happens when the page renders again, for example after a state change in a parent?

                Choose an answer to see the explanation.

              4. Question 4 of 7Is this component pure?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                export default function Basket({ names = ["Tea", "Cake"] }) {
                  const items = [];
                  for (const name of names) {
                    items.push(<li key={name}>{name}</li>);
                  }
                  return <ul>{items}</ul>;
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7What does TopSeller show?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                const drinks = ["Tea", "Coffee", "Lemonade"];
                
                function AToZ({ items }) {
                  return <p>A to Z: {items.sort().join(", ")}</p>;
                }
                
                function TopSeller({ items }) {
                  return <p>Top seller: {items[0]}</p>;
                }
                
                export default function App() {
                  return (
                    <>
                      <AToZ items={drinks} />
                      <TopSeller items={drinks} />
                    </>
                  );
                }

                Choose an answer to see the explanation.

              6. Question 6 of 7You want a Place order button to send the cart to the server. Where does the fetch call go?

                Choose an answer to see the explanation.

              7. Question 7 of 7You have React DevTools installed and StrictMode on. How do the logs from a component's second call look in your browser's console?

                Choose an answer to see the explanation.

              Key takeaways

              • A pure component changes nothing that existed before the call, and returns the same JSX for the same props.
              • Changing a variable from outside while rendering makes the result depend on how often, and in what order, React rendered: the numbered cart climbed on every render.
              • In development, StrictMode calls each component twice in a row and keeps the second result, so impure rendering shows at once. A production build calls it once.
              • This page's console prints both calls; with React DevTools installed, the second call's logs are dimmed.
              • Side effects go in event handlers first, and in Effects, which run after the render reaches the page, as a last resort.
              • Changing arrays and variables you made during the same render is fine. Copy a prop before you sort it.

              Remember the one-liner.
              Rendering only calculates; changes happen in handlers and Effects.

              Coming next: Your UI as a tree. It shows how React sees your components as a tree, and why that tree explains rendering, state, and performance.

              CompleteFrontend Clear concepts. Working examples.