# Week 7

Last week, our page asked a server for a list and showed it. This week we're building the thing every store has: a cart. And we're building it together, one piece at a time. You'll start with a page that just shows a shirt. By the end of the meeting you'll be able to pick a size, add it to a cart, add it again and watch the quantity go up, and remove it. Every one of those is a few lines you typed yourself.

The products and prices are made up, and the cart only lives in React state (so refreshing the page empties it). Adding something doesn't reserve a shirt, place an order, or charge anyone. We'll get to the real store cart later.

## Getting started

Save and commit your Week 6 work. Open a terminal at the top of `<span class="editor-theme-code">practice-page</span>` and make a new branch from it:

```sh
git status
git switch -c cart-practice
```

If Git says that branch already exists, use `<span class="editor-theme-code">git switch cart-practice</span>` instead.

Now we need a starting point. In `<span class="editor-theme-code">react-page/src</span>`, replace `<span class="editor-theme-code">App.jsx</span>` and `<span class="editor-theme-code">App.css</span>` with the starter versions below, and make a new file called `<span class="editor-theme-code">products.js</span>`. Keep `<span class="editor-theme-code">main.jsx</span>` and `<span class="editor-theme-code">styles.css</span>` the way they are. (Your Week 6 work is still saved in Git, so nothing is lost.) The same three files are also in [week-07-starter.zip](https://wiki.sitblueprint.com/attachments/24) if you'd rather download than copy.

### `<span class="editor-theme-code">src/products.js</span>`

```js
// Made-up practice products. Prices are in cents, so 2400 means $24.00.
export const products = [
  {
    id: "demo-shirt",
    name: "Practice T-shirt",
    variants: [
      { id: "demo-shirt-small", label: "Small", priceMinor: 2400, available: true },
      { id: "demo-shirt-medium", label: "Medium", priceMinor: 2400, available: true },
      { id: "demo-shirt-large", label: "Large", priceMinor: 2400, available: false },
    ],
  },
];
```

### `<span class="editor-theme-code">src/App.css</span>`

```css
section + section {
  margin-top: 24px;
}

label {
  display: block;
  font-weight: 600;
  margin: 16px 0 8px;
}

select {
  display: block;
  width: 100%;
  font: inherit;
  color: inherit;
  background: white;
  padding: 12px;
  border: 1px solid #64748b;
  border-radius: 8px;
  margin-bottom: 16px;
}

select:focus-visible {
  outline: 3px solid #1d4ed8;
  outline-offset: 3px;
}

.remove-button {
  color: #1d4ed8;
  background: #eff6ff;
  border: 1px solid #1d4ed8;
}

.cart-subtotal {
  font-size: 1.2rem;
  font-weight: 700;
}

button:disabled {
  background: #475569;
  cursor: not-allowed;
}
```

### `<span class="editor-theme-code">src/App.jsx</span>` (starter)

```javascript
import "./App.css";

const product = products[0];

function formatPrice(priceMinor) {
  return (priceMinor / 100).toLocaleString("en-US", {
    style: "currency",
    currency: "USD",
  });
}

export default function App() {
  return (
    <main>
      <h1>Practice cart</h1>
      <p>Made-up products. This cart lives in React state, so refreshing the page empties it.</p>

      <section className="interest-card" aria-labelledby="product-heading">
        <h2 id="product-heading">{product.name}</h2>
        <p>{formatPrice(product.variants[0].priceMinor)}</p>
        {/* Step 1: the size dropdown goes here */}
      </section>

      <section aria-labelledby="cart-heading">
        <h2 id="cart-heading">Your cart</h2>
        <p>Your cart is empty.</p>
      </section>
    </main>
  );
}
```

Start the server from `<span class="editor-theme-code">react-page</span>`:

```sh
cd react-page
npm run dev
```

Open the address the terminal prints. You should see **Practice cart**, a Practice T-shirt for $24.00, and "Your cart is empty." That's it for now. There's no dropdown and no button yet, because that's what we're about to write. If `<span class="editor-theme-code">npm</span>` isn't found, check the Setup page.

Leave the dev server running the whole time. Every time you save, the page updates by itself.

## What a cart has to know

A product list and a cart sound like the same thing, but they do pretty different jobs. The product list describes what you *can* buy. The cart describes what *this person* picked and how many.

Open `<span class="editor-theme-code">products.js</span>`. There's one product, the Practice T-shirt, and it has three **variants**. A variant is one specific option of a product that you can actually buy, like the Small T-shirt. All three sizes belong to `<span class="editor-theme-code">demo-shirt</span>`, but each one has its own variant ID.

That's the big idea for today: the cart needs to remember the variant, not just the product. If we only saved `<span class="editor-theme-code">demo-shirt</span>`, a Small and a Medium would look like the same thing, and someone ordering one of each would get two of whatever we guessed.

`<span class="editor-theme-code">available</span>` is a Boolean (`<span class="editor-theme-code">true</span>` or `<span class="editor-theme-code">false</span>`). Large is `<span class="editor-theme-code">false</span>`, so it'll show up as sold out.

`<span class="editor-theme-code">priceMinor</span>` is the price in cents, so `<span class="editor-theme-code">2400</span>` means $24.00. We store whole cents because decimals in JavaScript can get weird (try `<span class="editor-theme-code">0.1 + 0.2</span>` in the Console sometime). `<span class="editor-theme-code">formatPrice</span>` in the starter turns cents into dollars, and it's the only place we divide by 100. These field names are our practice format, not Fourthwall's. The real API has its own fields, and you always go by its docs.

![The demo-shirt product has three variants. Small, demo-shirt-small, available true. Medium, demo-shirt-medium, available true. Large, demo-shirt-large, available false, so it shows as sold out. Adding Small makes a cart line with variantId demo-shirt-small, label Small, quantity 1 and unitPriceMinor 2400. Adding Small again raises that same line to quantity 2. Adding Medium makes a second, separate line. 2400 means $24.00, and these fields are our practice format, not Fourthwall's.](https://wiki.sitblueprint.com/uploads/images/gallery/2026-11/vjcproduct-variant-cart.png)

Here's what we're building toward. The cart line remembers the variant ID. Adding Small again doesn't make a second line, it just bumps the quantity. Adding Medium does make a second line.

## Step 1: Pick a size

First, the page needs to remember which size is picked. That's state, same as the search box in Week 5.

At the top of `<span class="editor-theme-code">App.jsx</span>`, add the `<span class="editor-theme-code">useState</span>` import above the `<span class="editor-theme-code">products</span>` import, so the imports look like this:

```jsx
import { useState } from "react";
import { products } from "./products.js";
```

Next, inside `<span class="editor-theme-code">App</span>`, right under the `<span class="editor-theme-code">export default function App() {</span>` line, add the state and a function to update it:

```jsx
const [selectedVariantId, setSelectedVariantId] = useState(product.variants[0].id);

  function handleVariantChange(event) {
    setSelectedVariantId(event.target.value);
  }
```

`<span class="editor-theme-code">selectedVariantId</span>` starts as the first variant's ID, so the page starts on Small.

Now find these two lines in the product section:

```jsx
<p>{formatPrice(product.variants[0].priceMinor)}</p>
        {/* Step 1: the size dropdown goes here */}
```

Delete both of them and put this in their place:

```jsx
<label htmlFor="variant">Size</label>
        <select id="variant" value={selectedVariantId} onChange={handleVariantChange}>
          {product.variants.map((variant) => (
            <option key={variant.id} value={variant.id} disabled={!variant.available}>
              {variant.label}, {formatPrice(variant.priceMinor)}
              {!variant.available && " (sold out)"}
            </option>
          ))}
        </select>
        <p>Selected: {selectedVariantId}</p>
```

Save. You should see a Size dropdown with Small, Medium, and Large, and Large should be greyed out with "(sold out)". Change it to Medium, and the "Selected:" line should change to `<span class="editor-theme-code">demo-shirt-medium</span>`.

So what did we just write? This is the same controlled input from Week 5, just a dropdown this time. `<span class="editor-theme-code">value</span>` comes from state, and `<span class="editor-theme-code">handleVariantChange</span>` puts `<span class="editor-theme-code">event.target.value</span>` back into state. We `<span class="editor-theme-code">.map()</span>` over the variants to make one `<span class="editor-theme-code"><option></span>` each, and each one needs a `<span class="editor-theme-code">key</span>`, like our interest cards did.

Each option's `<span class="editor-theme-code">value</span>` is the variant ID, and the text between the tags is the label people read. That way we could rename "Small" to "S" and the cart wouldn't care, because the ID stays the same.

`<span class="editor-theme-code">!</span>` flips a Boolean, so a sold-out variant gives `<span class="editor-theme-code">disabled={true}</span>`. Same idea with `<span class="editor-theme-code">&&</span>` from Week 5: the "(sold out)" text only shows up when the variant isn't available.

The "Selected:" line is just there so we can see the state. We'll delete it in Step 3.

## Step 2: Add to cart

Now we need the cart itself. The cart is a list, so it starts as an empty array. Right under the `<span class="editor-theme-code">const [selectedVariantId, setSelectedVariantId] = useState(...)</span>` line, add:

```jsx
const [cart, setCart] = useState([]);
```

Now the function that runs when someone clicks Add. Put this right after the closing `<span class="editor-theme-code">}</span>` of `<span class="editor-theme-code">handleVariantChange</span>`:

```jsx
function handleAdd(variantId) {
    const variant = product.variants.find((option) => option.id === variantId);

    setCart([
      ...cart,
      {
        variantId: variant.id,
        name: product.name,
        label: variant.label,
        quantity: 1,
        unitPriceMinor: variant.priceMinor,
      },
    ]);
  }
```

Now the button. Right under the `<span class="editor-theme-code"><p>Selected: {selectedVariantId}</p></span>` line, add:

```jsx
<button type="button" onClick={() => handleAdd(selectedVariantId)}>
          Add to cart
        </button>
```

Last piece: we need to actually show the cart. In the cart section, find:

```jsx
<p>Your cart is empty.</p>
```

Replace it with:

```jsx
{cart.length === 0 && <p>Your cart is empty.</p>}
        <div className="interest-list">
          {cart.map((line) => (
            <article className="interest-card" key={line.variantId}>
              <h3>{line.name}, {line.label}</h3>
              <p>{formatPrice(line.unitPriceMinor)} each</p>
              <p>Quantity: {line.quantity}</p>
            </article>
          ))}
        </div>
```

Save. Click **Add to cart** once. You should see one Practice T-shirt, Small, quantity 1.

Now let's look at what we just typed, top to bottom.

`<span class="editor-theme-code">.find()</span>` goes through an array and gives back the first item that passes your test. Here, it's the variant whose ID matches the one we picked.

Then we build the next cart. `<span class="editor-theme-code">...cart</span>` is spread syntax. It copies everything from the old array into a new one, and our new line goes at the end. We make a new array instead of calling `<span class="editor-theme-code">cart.push(...)</span>`, because `<span class="editor-theme-code">push</span>` changes the array React is already holding. React compares the old and new state to decide whether to render again, so if you edit the old array in place, the page might not update.

In the button, the arrow (`<span class="editor-theme-code">() => ...</span>`) matters. It's a tiny function that waits for the click. If you wrote `<span class="editor-theme-code">onClick={handleAdd(selectedVariantId)}</span>`, it would run while React is drawing the page, before anyone clicked. Same mistake as `<span class="editor-theme-code">onChange={handleSearch()}</span>` in Week 5.

The cart list is the same `<span class="editor-theme-code">.map()</span>` as our interest cards. The empty message only shows up when there's nothing in the cart, and each line's `<span class="editor-theme-code">key</span>` is its `<span class="editor-theme-code">variantId</span>`.

Now click it again.

### Two lines?

You should see *two* Small lines, each with quantity 1. Open the browser's Console (right-click, Inspect, Console). React is complaining: "Encountered two children with the same key."

That's the bug we wanted to see. Our `<span class="editor-theme-code">handleAdd</span>` always adds a new line, even when that size is already in the cart. React is mad because we told it `<span class="editor-theme-code">variantId</span>` is what makes each line unique, and now two lines have the same one.

Refresh the page to empty the cart. Now let's fix it.

In `<span class="editor-theme-code">handleAdd</span>`, find this line:

```jsx
const variant = product.variants.find((option) => option.id === variantId);
```

Right under it, add:

```jsx
if (!variant || !variant.available) {
      return;
    }

    const existingLine = cart.find((line) => line.variantId === variantId);
    if (existingLine) {
      setCart(
        cart.map((line) => {
          if (line.variantId === variantId) {
            return { ...line, quantity: line.quantity + 1 };
          }
          return line;
        })
      );
      return;
    }
```

Leave the `<span class="editor-theme-code">setCart([...cart, { ... }])</span>` that was already there, right below what you just pasted. That's still how a brand-new size gets added. Your whole `<span class="editor-theme-code">handleAdd</span>` should now look like this (check your braces against it):

```jsx
function handleAdd(variantId) {
    const variant = product.variants.find((option) => option.id === variantId);
    if (!variant || !variant.available) {
      return;
    }

    const existingLine = cart.find((line) => line.variantId === variantId);
    if (existingLine) {
      setCart(
        cart.map((line) => {
          if (line.variantId === variantId) {
            return { ...line, quantity: line.quantity + 1 };
          }
          return line;
        })
      );
      return;
    }

    setCart([
      ...cart,
      {
        variantId: variant.id,
        name: product.name,
        label: variant.label,
        quantity: 1,
        unitPriceMinor: variant.priceMinor,
      },
    ]);
  }
```

Read it top to bottom:

1. If the variant doesn't exist or isn't available, stop. `<span class="editor-theme-code">return</span>` ends the function early, so nothing gets added.
2. Look for a cart line that already has this variant ID.
3. If there is one, make a new cart where that line's quantity is 1 higher, and stop there.
4. If there isn't, we fall through to the `<span class="editor-theme-code">setCart([...cart, ...])</span>` we already had, which adds a new line.

`<span class="editor-theme-code">.map()</span>` visits every line and returns one result for each. For the line we're bumping, `<span class="editor-theme-code">{ ...line, quantity: line.quantity + 1 }</span>` makes a new object with all the old fields copied in, then replaces `<span class="editor-theme-code">quantity</span>`. Every other line comes back the way it was. In Week 5, `<span class="editor-theme-code">.map()</span>` turned data into cards. Here it turns data into new data. Same method, it just depends on what you return.

Don't write `<span class="editor-theme-code">existingLine.quantity = existingLine.quantity + 1</span>`. That edits an object React is already holding, which is the same problem as `<span class="editor-theme-code">push</span>`.

You might wonder why we check `<span class="editor-theme-code">available</span>` when the dropdown already greys out Large. The disabled option helps the person using the page. The check in the function stops any other code from adding something it shouldn't. (A real store checks again on its server, since anyone can mess with what a browser sends.)

Save, then try it:

1. Clear the Console (the circle-with-a-line icon at its top left) so the old warning is gone. Add Small twice. You should see **one** line with quantity 2, and no new warning.
2. Pick Medium and add it. You should see a second, separate line for Medium.

That's the variant idea working. If Small and Medium ever end up on one line, something is matching on the product ID instead of the variant ID.

## Step 3: Move the button into its own component

Right now the button lives inside `<span class="editor-theme-code">App</span>`. On the merch site, one person might build the product controls and someone else the cart, so let's practice splitting the button out. The catch is that the button needs to change the cart, and the cart lives in `<span class="editor-theme-code">App</span>`.

So far, props have carried text, like a card's title. Props can also carry a function. That's how a child tells its parent that something happened.

Above `<span class="editor-theme-code">export default function App() {</span>` (outside `<span class="editor-theme-code">App</span>`, not inside it), add:

```jsx
function AddButton(props) {
  function handleClick() {
    props.onAdd(props.variantId);
  }

  return (
    <button type="button" onClick={handleClick}>
      Add to cart
    </button>
  );
}
```

Now, back in `<span class="editor-theme-code">App</span>`'s JSX, find the debug line and the button from Step 2. That's everything from `<span class="editor-theme-code"><p>Selected:</span>` down to the button's `<span class="editor-theme-code"></button></span>`:

```jsx
<p>Selected: {selectedVariantId}</p>
        <button type="button" onClick={() => handleAdd(selectedVariantId)}>
          Add to cart
        </button>
```

Delete all of that and replace it with:

```jsx
<AddButton variantId={selectedVariantId} onAdd={handleAdd} />
```

Save. The "Selected:" line is gone, but Add should work exactly like before. Small twice still gives quantity 2, and Medium still gets its own line.

Here's what happens when you click:

![App owns selectedVariantId and cart. It passes the selected ID and its handleAdd function to AddButton as props. A browser click calls the child handleClick, which calls props.onAdd(props.variantId). That runs App handleAdd with demo-shirt-small, which finds the variant and calls setCart with a new array. React renders the updated cart. Passing onAdd={handleAdd} does not call it during rendering.](https://wiki.sitblueprint.com/uploads/images/gallery/2026-11/9Crcallback-to-parent.png)

`<span class="editor-theme-code">App</span>` hands the button two things: the selected ID, and its own `<span class="editor-theme-code">handleAdd</span>` function. When someone clicks, the child calls `<span class="editor-theme-code">props.onAdd(...)</span>`, which is really `<span class="editor-theme-code">App</span>`'s `<span class="editor-theme-code">handleAdd</span>`, and passes it the ID. The button never touches the cart itself.

Why not just give `<span class="editor-theme-code">AddButton</span>` its own cart? Because lots of parts of the page need the same cart. The product controls add to it, the cart list shows it, and later a checkout button reads it. If each one kept its own copy, they'd disagree. So the cart lives in the closest parent that all of them share (here that's `<span class="editor-theme-code">App</span>`), and the children get what they need through props. React's docs call moving state up to that shared parent "lifting state up."

`<span class="editor-theme-code">onAdd</span>` is a name we made up. React doesn't know it means anything. `<span class="editor-theme-code">onClick</span>` on the real `<span class="editor-theme-code"><button></span>` is the built-in one that listens for the browser's click.

Keep it as `<span class="editor-theme-code">onAdd={handleAdd}</span>`, with no parentheses. `<span class="editor-theme-code">onAdd={handleAdd(selectedVariantId)}</span>` would call it right away while React is drawing the page.

## Step 4: Remove an item

Each cart line needs a Remove button, and the button needs to tell us *which* line.

Inside `<span class="editor-theme-code">App</span>`, right above the `<span class="editor-theme-code">return (</span>` that starts the page's JSX (the one with `<span class="editor-theme-code"><main></span>` under it, not the one in `<span class="editor-theme-code">AddButton</span>`), add:

```jsx
function handleRemove(variantId) {
    setCart(cart.filter((line) => line.variantId !== variantId));
  }
```

This is `<span class="editor-theme-code">.filter()</span>` from Week 5. It makes a new array with only the items that pass the test. `<span class="editor-theme-code">!==</span>` means "is not equal to." So read it as "keep every line whose variant ID isn't the one we're removing."

`<span class="editor-theme-code">(line) => line.variantId !== variantId</span>` is an arrow function. `<span class="editor-theme-code">line</span>` is the parameter, and whatever comes after the arrow is what it returns. It does the same job as the named `<span class="editor-theme-code">matchesSearch</span>` function we passed to `<span class="editor-theme-code">.filter()</span>` in Week 5. We just wrote it right inside the call because we only need it there.

Now the button. In the cart list, right under `<span class="editor-theme-code"><p>Quantity: {line.quantity}</p></span>`, add:

```jsx
<button
                type="button"
                className="remove-button"
                onClick={() => handleRemove(line.variantId)}
              >
                Remove
              </button>
```

Each line passes its own `<span class="editor-theme-code">variantId</span>`, so we wrap the call in an arrow again. That arrow waits for the click.

Save. Add Small and Medium, then click Remove on Small. Medium should still be there. Remove Medium too, and you should see "Your cart is empty." again.

## When the cart lives on a server

Our cart runs instantly in the browser. The merch site's cart will be different, because Fourthwall holds it. Remember these from last week?

<table id="bkmrk-endpointwhat-it-does"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Endpoint

</th><th>What it does

</th></tr><tr><td>`<span class="editor-theme-code">POST /v1/carts/{cartId}/add</span>`

</td><td>Add items to a cart

</td></tr><tr><td>`<span class="editor-theme-code">POST /v1/carts/{cartId}/change</span>`

</td><td>Change item quantities

</td></tr><tr><td>`<span class="editor-theme-code">POST /v1/carts/{cartId}/remove</span>`

</td><td>Remove items

</td></tr></tbody></table>

Their request bodies look pretty familiar. Each one sends a list of `<span class="editor-theme-code">items</span>`, and each item has a `<span class="editor-theme-code">variantId</span>` and a `<span class="editor-theme-code">quantity</span>`. So today's idea (the cart cares about the variant, not the product) carries straight over.

What comes back is the whole cart. Each item has a `<span class="editor-theme-code">variant</span>` (with its own `<span class="editor-theme-code">id</span>`, `<span class="editor-theme-code">name</span>`, and `<span class="editor-theme-code">unitPrice</span>`) and a `<span class="editor-theme-code">quantity</span>`. There's no subtotal field in the cart response, so any total you add up in the browser is only an estimate. The real total comes from checkout.

A few things change once the cart is on a server:

1. The click sends a request. In the version we'll build, the page waits for the answer before changing the cart.
2. The server can say no (out of stock, bad quantity), so you show the error and keep the last cart that actually worked.
3. You update the page from the cart the server sends back, not from the number you *hoped* it would be.
4. While a request is going, you disable the cart buttons so two changes don't race each other. (Same idea as disabling **Load interests** last week.)

And a cart still isn't an order, whether it's ours or Fourthwall's. Next week, we'll follow it from the cart into checkout and see what happens to payment and orders.

## When something goes wrong

<table id="bkmrk-what-you-seewhat-to-"><colgroup><col></col><col></col></colgroup><tbody><tr><th>What you see

</th><th>What to check

</th></tr><tr><td>The page goes blank and the Console says

`<span class="editor-theme-code">useState is not defined</span>`

</td><td>Add the import from Step 1 at the top of

`<span class="editor-theme-code">App.jsx</span>`

, with the other imports (not inside

`<span class="editor-theme-code">App</span>`

).

</td></tr><tr><td>Clicking does nothing and the Console says

`<span class="editor-theme-code">cart is not defined</span>`

 or

`<span class="editor-theme-code">setCart is not defined</span>`

</td><td>The function got pasted outside

`<span class="editor-theme-code">App</span>`

 (after its last

`<span class="editor-theme-code">}</span>`

). Move it inside

`<span class="editor-theme-code">App</span>`

, above

`<span class="editor-theme-code">return (</span>`

.

</td></tr><tr><td>A red error about an unexpected token or

`<span class="editor-theme-code">}</span>`

</td><td>Usually a paste landed inside the wrong tag or a brace went missing. Compare that spot with the snippet, or grab the catch-up copy for the step you're on.

</td></tr><tr><td>The dropdown changes, but Add uses the wrong size

</td><td>The

`<span class="editor-theme-code">select</span>`

 should read and set

`<span class="editor-theme-code">selectedVariantId</span>`

, and

`<span class="editor-theme-code">AddButton</span>`

 should get that same ID.

</td></tr><tr><td>Same size twice still makes two lines

</td><td>The

`<span class="editor-theme-code">existingLine</span>`

 check from Step 2 has to come before the

`<span class="editor-theme-code">setCart([...cart, ...])</span>`

 at the bottom of

`<span class="editor-theme-code">handleAdd</span>`

, and it needs its

`<span class="editor-theme-code">return;</span>`

 after the

`<span class="editor-theme-code">cart.map</span>`

 part. Without that

`<span class="editor-theme-code">return</span>`

, both

`<span class="editor-theme-code">setCart</span>`

 calls run and the second one wins.

</td></tr><tr><td>Small and Medium become one line

</td><td>Check the

`<span class="editor-theme-code">existingLine</span>`

 find in

`<span class="editor-theme-code">handleAdd</span>`

. It has to compare

`<span class="editor-theme-code">line.variantId === variantId</span>`

, not the product ID.

</td></tr><tr><td>The page goes blank with "Too many re-renders" in the Console

</td><td>Something is calling a function while React draws the page. Pass the function (

`<span class="editor-theme-code">onAdd={handleAdd}</span>`

), and wrap the others in an arrow, like

`<span class="editor-theme-code">() => handleAdd(selectedVariantId)</span>`

.

</td></tr><tr><td>You click Add and the cart stays empty, with no error

</td><td>Check Remove's

`<span class="editor-theme-code">onClick</span>`

. Without the arrow,

`<span class="editor-theme-code">handleRemove(line.variantId)</span>`

 runs while the line is being drawn, so it removes itself instantly.

</td></tr><tr><td>Clicking does nothing to the cart

</td><td>Make a new array or object and call

`<span class="editor-theme-code">setCart</span>`

. If you edit the old one with

`<span class="editor-theme-code">push</span>`

 or

`<span class="editor-theme-code">existingLine.quantity = 2</span>`

, React may not notice anything changed.

</td></tr><tr><td>The price is 100 times too big

</td><td>The data is in cents. Divide by 100 only once, in

`<span class="editor-theme-code">formatPrice</span>`

.

</td></tr><tr><td>The cart is empty after refreshing

</td><td>That's expected. This cart only lives in React state.

</td></tr></tbody></table>

## Resources

Everything needed for today's code and explanations is above. These are extra examples or references to return to after the exercise.

- [React: Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)
- [React: Responding to Events](https://react.dev/learn/responding-to-events)
- [React: Updating Arrays in State](https://react.dev/learn/updating-arrays-in-state)
- [React: Updating Objects in State](https://react.dev/learn/updating-objects-in-state)
- [React: `<span class="editor-theme-code"><select></span>`](https://react.dev/reference/react-dom/components/select)
- [React: Rendering Lists (keys)](https://react.dev/learn/rendering-lists)
- [MDN: Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
- [MDN: Array.filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
- [MDN: Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- [MDN: Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
- [MDN: Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
- [Fourthwall: Add to Cart](https://docs.fourthwall.com/api-reference/storefront/carts/add-to-cart)
- [Fourthwall: Change Cart Quantity](https://docs.fourthwall.com/api-reference/storefront/carts/change-cart-quantity)
- [Fourthwall: Remove from Cart](https://docs.fourthwall.com/api-reference/storefront/carts/remove-from-cart)