Skip to Content
React in a Week
Day 2 · Describing the UI

Keeping components pure

Learn the one rule React relies on, that components only calculate and never change things while rendering, and how Strict Mode helps you follow it.

So far your components have taken props and returned JSX. They never changed anything outside themselves. That wasn't an accident. React is built around one assumption: given the same inputs, a component returns the same JSX, and rendering it has no other effects.

This lesson has fewer new tools than the others. Instead, it gives you a habit that prevents a whole family of confusing bugs later in the week.

Same inputs, same output

In math, a formula like y = 2x always gives the same y for the same x. Put in 3 and you get 6, every single time. Computing it doesn't change anything else in the world either.

A pure function is a JavaScript function that behaves the same way:

  • It minds its own business. It doesn't change any objects or variables that existed before it was called.
  • Same inputs, same output. Given the same arguments, it always returns the same result.
// Pure: only calculates from its input
function double(number) {
  return number * 2;
}

React components are meant to be pure functions of their props. Here ReadingTime turns a page count into an estimate. Given 300 pages, it always says the same thing, no matter how many times React calls it:

Live code· A pure component

A component that breaks the rule

Now look at a component that changes a variable declared outside itself while it renders:

Live code· An impure component

You would expect 1, 2 and 3. Instead you'll probably see 2, 4 and 6. Each Ticket gives a different result even though it got exactly the same (empty) props. The output now depends on how many times React happened to call it, which is something you don't control.

The fix is to stop counting during render. Pass the number in as a prop, so the output depends only on inputs:

Live code· The pure fix

Why did it count twice?

The sandboxes in this course, like most new React projects, wrap the app in Strict Mode. In development, Strict Mode deliberately calls each of your component functions twice and keeps one of the results. A pure component gives the same answer both times, so you never notice. An impure one gives itself away, as Ticket did.

This only happens in development. In production, Strict Mode does nothing, so it doesn't slow down your users' app. If you see a console.log inside a component appear twice, this is the reason, and it is not a bug in your code.

Deep diveWhy does React care so much?

React assumes it can call your components whenever it likes, as many times as it likes, and in any order. That freedom is what makes some of its best features possible:

  • It can skip rendering a component whose inputs didn't change, because the output would be identical.
  • It can pause a render that is no longer needed and start over with fresher data.
  • It can render the same components on a server and in the browser and get matching results.

All of these break if rendering also changes things. You'll meet these features later in the week, including the React Compiler on Day 6, which relies on your components being pure.

Don't change props or shared data during render

The Ticket example changed a plain variable. A sneakier version of the same mistake is changing an array or object that came from somewhere else, such as your props or an imported data file. Some array methods do this without making it obvious. sort and reverse rearrange the original array in place.

In this sandbox, SortedList sorts the books by title. Watch what happens to the "original order" list below it:

Live code· sort changes the original array

Both lists come out sorted. SortedList reached into the shared books array and reordered it, so every other component that reads books now sees the changed order. Nothing in OriginalOrder explains why its output changed, which is what makes this kind of bug so hard to track down.

Local mutation is fine

Purity is about not changing things that existed before the render. Variables and arrays you create inside the component during this render are yours to change however you like. No one else can see them yet:

function Shelf({ books }) {
  const items = []; // created during this render
  for (const book of books) {
    items.push(<li key={book.id}>{book.title}</li>); // fine
  }
  return <ul>{items}</ul>;
}

React's docs call this local mutation: your component's little secret.

Quick check

Which of these is safe to do while a component is rendering?

So where do changes happen?

Apps obviously need to change things: update the screen, save data, start animations. Those changes are called side effects, because they happen on the side rather than as part of calculating the output.

In React, side effects belong outside of rendering:

  • Most of them go in event handlers, functions that run when the user clicks or types. React never calls event handlers during rendering, so they don't need to be pure. You'll write them first thing on Day 3.
  • To change what is on screen, you set state instead of changing variables. That is also Day 3.
  • For the rare effect that has no event to belong to, such as syncing with a browser API, there is useEffect on Day 4. Treat it as a last resort.

For the rest of today, the rule is simple: components take props and return JSX, and nothing else.

Try it

Fix the impure ranking

This list should number the books 1 to 4, but it shows the wrong numbers. Find the impure code and fix it so the ranking is correct. You won't need any variable outside the components.

Hint: map passes a second argument to your function, the index of the current item.

Live code· Challenge: ranking
Show solution

The module-level rank variable changes during every render, so its value depends on how many times React has rendered. Instead, compute the rank from the index that map already provides and pass it down as a prop:

Live code· Solution: ranking

Using the index to display a position is fine. The key is still book.id, because the key identifies the book, while rank only describes where it sits right now.

Quick check

In development, a console.log inside your component prints twice for every render. What is the most likely reason?

Day 2 progress

0/7
Next: Styling components

On this page