cf.completefrontend
Open lab My progress
THE REACT FIELD GUIDE

Updating arrays in state

Add, remove, replace, insert, and sort items in a React state array without mutating it, and update objects inside arrays safely, in live examples.

By the end, you can
  • 01
    Pick array methods that are safe for stateTell the methods that change an array, like push, splice, and sort, from the ones that return a new array, like filter, map, and toSorted.
  • 02
    Make every kind of list changeAdd, remove, replace, transform, insert, sort, and reverse items in an array in state by building a new array each time.
  • 03
    Update objects inside an arrayChange one object in a list with map and spread, and explain why copying only the array still changes objects other code shares.

A shopping list that won't grow

An array is an object too, so everything in Updating objects in state applies to it. Arrays just have more ways to be changed by accident: many of their methods change the array they're called on. Here is a shopping list that adds eggs with push. Click Add eggs twice and watch the console under the result.

A list that adds with push (a bug on purpose)Pop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; export default function App() {  const [items, setItems] = useState(["Milk", "Bread"]);   function addEggs() {    items.push("Eggs");    setItems(items);    console.log("items in the array:", items.length);  }   return (    <>      <p>Shopping list: {items.join(", ")}</p>      <button onClick={addEggs}>Add eggs</button>    </>  );}
ResultLoading…

Line 4 keeps the list in state. Line 7 pushes "Eggs" onto the end of the array React already has, and line 8 hands React that same array. React compares it with what it has using Object.is, finds the same array, and skips the render. So line 14 keeps showing Milk, Bread, while line 9 prints items in the array: 3, then 4. Here is the fix:

The same list, with a new arrayPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; export default function App() {  const [items, setItems] = useState(["Milk", "Bread"]);   function addEggs() {    setItems([...items, "Eggs"]);  }   return (    <>      <p>Shopping list: {items.join(", ")}</p>      <button onClick={addEggs}>Add eggs</button>    </>  );}
ResultLoading…

Line 7 builds a new array with spread: ...items copies every item into it, and "Eggs" goes at the end. React gets a different array and renders: Milk, Bread, Eggs. Every kind of list change in this lesson works the same way: read the old array, build a new one, and hand the new one to the set function.

Left: a crate named items holding Milk, Bread and Eggs. Three arrows lead to three new crates: filter without Bread gives Milk and Eggs, a spread with Tea gives Milk, Bread, Eggs and Tea, and toSorted gives Bread, Eggs and Milk.MilkBreadEggsitemsitems.filter(…)without BreadMilkEggsnew[...items, "Tea"]Tea at the endMilkBreadEggsTeanewitems.toSorted()a sorted copyBreadEggsMilknew
Three updates, three new arrays: filter leaves out Bread, the spread adds Tea, and toSorted sorts a copy. items, on the left, still holds Milk, Bread, and Eggs.
Real-life analogyA printed timetable at a station

A station doesn't scribble on the timetable on the wall. When a train changes, it prints a new timetable from the old one with the change in it, puts the new sheet up, and files the old one away exactly as it was. Anyone who looked at the old sheet still has the right picture of yesterday.

In real life: The timetable on the wall
In React: The array in state
In real life: Crossing out a line with a pen
In React: items.push(…), items.splice(…), items.sort()
In real life: Printing a new timetable from the old one
In React: [...items, "Eggs"], items.filter(…), items.toSorted()
In real life: The station swaps the sheet on the wall
In React: React renders with the new array
In real life: Yesterday's sheet, filed away as it was
In React: The old array, untouched
The main idea

Treat an array in state as read-only. Don't call methods that change it, like push, splice, or sort, and don't assign to its items. Build a new array, with spread or with methods that return one, like filter, map, and toSorted, and pass that to the set function.

Which array methods change the array

Each row is one job. The middle column lists the ways that change the array they're called on: don't use them on an array in state. The right column lists the ways that leave it alone and give you a new array:

Array methods: change the array, or return a new one
JobChanges the array (avoid on state)Returns a new array (use on state)
Add an itempush, unshift[...items, item], [item, ...items], concat
Remove an itempop, shift, splicefilter, slice, toSpliced
Replace an itemitems[index] = item, splicemap, with
Insert at a positionsplice[...items.slice(0, index), item, ...items.slice(index)], toSpliced
SortsorttoSorted, or [...items].sort()
ReversereversetoReversed, or [...items].reverse()

Watch out for slice and splice: the names are one letter apart, but slice copies part of an array into a new one, and splice removes or inserts items in the array itself. In React you'll use slice far more often. The Slicing, splicing and joining lesson covers both.

Four methods in the right column are newer: toSorted, toReversed, toSpliced, and with. Each is the copying version of a method that changes the array: sort, reverse, splice, and items[index] = value. They were added to JavaScript in 2023 (the ES2023 edition), and every current browser has them: Chrome and Edge since version 110, Firefox since 115, and Safari since 16. Run this:

The four copying methods leave the array alonePop out in the code editor (opens in a new tab)JavaScript
const items = ["Milk", "Bread", "Eggs"];

console.log(items.toSorted().join(", "));
console.log(items.toReversed().join(", "));
console.log(items.with(1, "Tea").join(", "));
console.log(items.toSpliced(1, 1).join(", "));
console.log(items.join(", "));

Line 3 prints Bread, Eggs, Milk, a sorted copy. Line 4 prints Eggs, Bread, Milk, a reversed copy. Line 5 prints Milk, Tea, Eggs: with(1, "Tea") is a copy with position 1 replaced (positions start at 0). Line 6 prints Milk, Eggs: toSpliced(1, 1) is a copy with one item removed at position 1. Line 7 prints Milk, Bread, Eggs: after all that, items hasn't changed. The Sorting and copying safely lesson covers these methods in full, with what to do for older browsers.

Try any method from the table. The playground runs it for real, in this browser, on a fresh ["Milk", "Bread", "Eggs"], and shows what it did to items and what it returned:

Playground: does this method change the array?
The method you picked, on line 2JavaScript
const items = ["Milk", "Bread", "Eggs"];const result = items.push("Tea");
What happeneditems changed
items now holds
["Milk", "Bread", "Eggs", "Tea"]
result
4
Is result the same array as items?
result isn't an array
Try it yourself

push changed items itself, and returned 4. On an array in state, that's a mutation: copy first, or use a method that returns a new array.

Each pick runs line 2 for real, in this browser, on a fresh items array. Methods that change items aren't safe on an array in state; methods that leave it alone and return a new array are.

Look at sort and reverse: they change items and return the same array. So setItems(items.sort()) looks like it passes a sorted list, but it passes the array React already has, changed. And push returns the new length, 4, not an array at all.

Adding and removing items

Real lists hold objects with an id, so each item has a stable key (the Rendering lists lesson explains why). Type an item, add it to either end, and remove a few:

A grocery list: add to either end, remove with filterPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; let nextId = 3; export default function App() {  const [name, setName] = useState("");  const [items, setItems] = useState([    { id: 0, name: "Milk" },    { id: 1, name: "Bread" },    { id: 2, name: "Eggs" },  ]);   function addToEnd() {    setItems([...items, { id: nextId++, name }]);    setName("");  }   function addToStart() {    setItems([{ id: nextId++, name }, ...items]);    setName("");  }   function remove(id) {    setItems(items.filter((item) => item.id !== id));  }   return (    <>      <label>        Item <input value={name} onChange={(event) => setName(event.target.value)} />      </label>      <button onClick={addToEnd} disabled={name === ""}>Add to end</button>      <button onClick={addToStart} disabled={name === ""}>Add to start</button>      <ul>        {items.map((item) => (          <li key={item.id}>            {item.name} <button onClick={() => remove(item.id)}>Remove</button>          </li>        ))}      </ul>    </>  );}
ResultLoading…

Line 3 keeps a counter for new ids, outside the component, so it isn't reset on each render. Lines 7 to 11 keep the list. Line 14 adds to the end: a new array with every old item, then the new one. Line 19 adds to the start: the new item first, then ...items. Spread does the job of both push and unshift, without changing items.

Line 24 removes an item with filter: it builds a new array of the items whose id isn't the one you clicked. Type Tea and click Add to end, then remove Bread: the list shows Milk, Eggs, and Tea. The add buttons stay off while the text box is empty (disabled on lines 32 and 33).

Replacing and transforming items with map

To change some or all of the items, use map. It calls your function for every item and builds a new array from what the function returns. Return the item as it is to keep it, or a new item to replace it. Click +1 next to Tea:

A cart: change one line, or every line, with mapPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; export default function App() {  const [cart, setCart] = useState([    { id: 0, name: "Tea", price: 3, quantity: 1 },    { id: 1, name: "Cake", price: 4, quantity: 2 },    { id: 2, name: "Lemonade", price: 2, quantity: 1 },  ]);   function addOne(id) {    const next = cart.map((item) => {      if (item.id !== id) return item;      return { ...item, quantity: item.quantity + 1 };    });    setCart(next);  }   function resetAll() {    setCart(cart.map((item) => ({ ...item, quantity: 1 })));  }   const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);   return (    <>      <ul>        {cart.map((item) => (          <li key={item.id}>            {item.name} × {item.quantity} <button onClick={() => addOne(item.id)}>+1</button>          </li>        ))}      </ul>      <p>Total: ${total}</p>      <button onClick={resetAll}>Set every quantity to 1</button>    </>  );}
ResultLoading…

addOne on lines 10 to 16 maps over the cart. Line 12 returns every other item as it is. Line 13 returns a new object for the item you clicked: ...item copies its fields, and the new quantity replaces the old one. Line 15 hands React the new array. The total on line 22 is worked out from the cart on every render, so it follows: Total: $13 becomes Total: $16.

Left, the old cart array: Tea × 1, Cake × 2, Lemonade × 1. Arrows to the right, the new array from map: Tea × 2 is a new object, and Cake × 2 and Lemonade × 1 are the same objects as before.cartcart.map(…)Tea × 1Tea × 2new objectCake × 2Cake × 2same objectLemonade × 1Lemonade × 1same object
After +1 on Tea: map builds a new array. Line 13 makes a new object for Tea, and line 12 returns the Cake and Lemonade objects as they are.

resetAll on lines 18 to 20 transforms every item: each one becomes a copy with quantity: 1. Click Set every quantity to 1: Total: $9. To replace the item at a known position instead of by id, with is shorter: items.with(1, "Tea") is a copy with Tea at position 1.

Inserting at a position

Spread puts a new item at the start or the end. To put one in the middle, take two slices of the old array: everything before the position, and everything from it on. Then spread both into a new array with the new item between them. Pick a position and click Insert:

A playlist: insert a song at any positionPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; let nextId = 3; export default function App() {  const [position, setPosition] = useState(1);  const [queue, setQueue] = useState([    { id: 0, title: "Morning Raga" },    { id: 1, title: "Chai Time" },    { id: 2, title: "Monsoon" },  ]);   function insertSong() {    const index = Math.min(Math.max(position, 0), queue.length);    const song = { id: nextId, title: `New song ${nextId}` };    nextId++;    setQueue([...queue.slice(0, index), song, ...queue.slice(index)]);  }   return (    <>      <label>        Position <input type="number" value={position} onChange={(event) => setPosition(Number(event.target.value))} />      </label>      <button onClick={insertSong}>Insert</button>      <ol>        {queue.map((song) => (          <li key={song.id}>{song.title}</li>        ))}      </ol>    </>  );}
ResultLoading…

Line 6 keeps the position from the number box on line 23, starting at 1. Line 14 keeps it between 0 and the length of the queue, so a position like -3 or 99 still works. Line 17 builds the new queue: queue.slice(0, index) is the songs before the position, then the new song, then queue.slice(index), the rest. Positions start at 0, so position 1 puts New song 3 second, after Morning Raga. Try position 0, then 99.

toSpliced does the same in one call: toSpliced(index, 0, song) removes 0 items at index and puts song there, in a copy. Run both:

Insert with two slices, or with toSplicedPop out in the code editor (opens in a new tab)JavaScript
const queue = ["Morning Raga", "Chai Time", "Monsoon"];

const withSlices = [...queue.slice(0, 1), "Rain Song", ...queue.slice(1)];
const withToSpliced = queue.toSpliced(1, 0, "Rain Song");

console.log(withSlices.join(", "));
console.log(withToSpliced.join(", "));
console.log(queue.join(", "));
Top: Morning Raga, from queue.slice(0, 1); the new item, Rain Song; and Chai Time and Monsoon, from queue.slice(1). Arrows lead down to one new array holding all four in that order.Morning Ragaslice(0, 1)Rain Songnew itemChai TimeMonsoonslice(1)Morning RagaRain SongChai TimeMonsoonnext
Line 3 of the example: everything before position 1, then the new song, then everything from position 1 on. The four go into a new array; queue still holds three songs.

Lines 6 and 7 print the same Morning Raga, Rain Song, Chai Time, Monsoon. Line 8 prints the queue itself, still three songs.

Sorting and reversing a copy

sort and reverse are the methods that most often change state by accident: they reorder the array itself, and return it. Sort a copy instead. Click Cheapest first, then Reverse:

Products: sort and reverse a copy, or sort in place (a bug on purpose)Pop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; export default function App() {  const [products, setProducts] = useState([    { id: 0, name: "Cake", price: 4 },    { id: 1, name: "Lemonade", price: 2 },    { id: 2, name: "Tea", price: 3 },  ]);   function cheapestFirst() {    setProducts(products.toSorted((a, b) => a.price - b.price));  }   function reverseOrder() {    setProducts(products.toReversed());  }   function cheapestFirstWithSort() {    setProducts(products.sort((a, b) => a.price - b.price));  }   return (    <>      <button onClick={cheapestFirst}>Cheapest first</button>      <button onClick={reverseOrder}>Reverse</button>      <button onClick={cheapestFirstWithSort}>Cheapest first with sort()</button>      <ol>        {products.map((product) => (          <li key={product.id}>            {product.name}: ${product.price}          </li>        ))}      </ol>    </>  );}
ResultLoading…

Line 11 uses toSorted with a function that compares prices (the Sorting and copying safely lesson explains the compare function): Lemonade: $2, Tea: $3, Cake: $4. Line 15 reverses a copy with toReversed: Cake, Tea, Lemonade. Before these methods, people wrote [...products].sort(…): copy, then sort the copy, which is local mutation and just as safe.

Now press Run again to start over, and click Cheapest first with sort(). Nothing changes: line 19 sorts the array in state itself, and sort returns that same array, so React skips the render. The page says Cake, Lemonade, Tea, but the array now holds Lemonade, Tea, Cake. Click Reverse: you'd expect the page reversed, Tea, Lemonade, Cake, but you get Cake, Tea, Lemonade, the reverse of the hidden sorted order.

Updating objects inside an array

Here is the bug that copying the array doesn't fix. An array holds references to its objects, not the objects themselves (the Objects and references lesson shows why). So [...list] is a new array whose slots point at the same objects. Change an object through the copy, and you've changed it everywhere it's used. Two friends keep lists of teas they've tried. Tick Green tea in Asha's list:

Two lists, one toggle that changes a shared object (a bug on purpose)Pop out in the code editor (opens in a new tab)JSX
import { useState } from "react";import TeaList from "./TeaList.jsx"; const teas = [  { id: 0, name: "Green tea", tried: false },  { id: 1, name: "Masala chai", tried: false },]; export default function App() {  const [ashaList, setAshaList] = useState(teas);  const [benList, setBenList] = useState(teas);   function toggle(list, setList, id) {    const next = [...list];    const tea = next.find((item) => item.id === id);    tea.tried = !tea.tried;    setList(next);  }   return (    <>      <h3>Asha has tried</h3>      <TeaList teas={ashaList} onToggle={(id) => toggle(ashaList, setAshaList, id)} />      <h3>Ben has tried</h3>      <TeaList teas={benList} onToggle={(id) => toggle(benList, setBenList, id)} />    </>  );}
ResultLoading…

Both lists start from the same teas on lines 4 to 7 (lines 10 and 11). The TeaList.jsx tab draws one list of checkboxes and calls onToggle with the tea's id. toggle on lines 13 to 18 looks careful: line 14 copies the array, and line 17 hands React the copy, so the page does update. But line 15 finds the Green tea object in the copy, which is the same object Ben's list holds, and line 16 changes it. Ben's Green tea is ticked too, though nobody touched his list.

Left, a crate named asha with two slots, 0 and 1. Right, a crate named ben with two slots. Arrows from both crates' slots point at the same two tea objects in the middle: Green tea, tried: true, and Masala chai, tried: false.two arrays, the same two objects[0][1]asha[0][1]benGreen tea, tried: trueMasala chai, tried: falsetick Green tea for Asha: Ben has it too
Both lists hold the same two tea objects. [...list] copies the array, not the objects, so line 16 changes the Green tea object that both lists show.

Two lists make the bug easy to see. With one list, it hides: the page looks right, but the old array's objects have changed, and anything still holding them, like an undo history or the previous render, now holds the new values. The fix is map, with a new object for the tea that changes:

The same lists, with a new object for the changed teaPop out in the code editor (opens in a new tab)JSX
import { useState } from "react";import TeaList from "./TeaList.jsx"; const teas = [  { id: 0, name: "Green tea", tried: false },  { id: 1, name: "Masala chai", tried: false },]; export default function App() {  const [ashaList, setAshaList] = useState(teas);  const [benList, setBenList] = useState(teas);   function toggle(list, setList, id) {    setList(list.map((tea) => (tea.id === id ? { ...tea, tried: !tea.tried } : tea)));  }   return (    <>      <h3>Asha has tried</h3>      <TeaList teas={ashaList} onToggle={(id) => toggle(ashaList, setAshaList, id)} />      <h3>Ben has tried</h3>      <TeaList teas={benList} onToggle={(id) => toggle(benList, setBenList, id)} />    </>  );}
ResultLoading…

Line 14 maps over the list. For the tea you clicked, it returns { ...tea, tried: !tea.tried }, a new object; every other tea is returned as it is. Tick Green tea for Asha: only Asha's list changes. The rule from Updating objects in state holds here too: copy every level you change, which here is the array and the one tea object.

Immer works on arrays as well. Your recipe can call find, assign to an item, even push or splice on the draft, and produce builds the new array and the new tea object. This is the same toggle with Immer, loaded from esm.sh:

The same lists, with Immer's producePop out in the code editor (opens in a new tab)JSX
import { useState } from "react";import { produce } from "immer";import TeaList from "./TeaList.jsx"; const teas = [  { id: 0, name: "Green tea", tried: false },  { id: 1, name: "Masala chai", tried: false },]; export default function App() {  const [ashaList, setAshaList] = useState(teas);  const [benList, setBenList] = useState(teas);   function toggle(list, setList, id) {    setList(      produce(list, (draft) => {        const tea = draft.find((item) => item.id === id);        tea.tried = !tea.tried;      }),    );  }   return (    <>      <h3>Asha has tried</h3>      <TeaList teas={ashaList} onToggle={(id) => toggle(ashaList, setAshaList, id)} />      <h3>Ben has tried</h3>      <TeaList teas={benList} onToggle={(id) => toggle(benList, setBenList, id)} />    </>  );}
ResultLoading…

Lines 17 and 18 find the tea in the draft and flip tried, just like the buggy version, but on the draft. Immer makes a new array and a new Green tea object, and Ben's list keeps its own.

A shopping cart with every kind of update

A cart page uses almost every update from this lesson. Add a few products (twice for one of them), sort, and remove one:

import { useState } from "react"; const shelf = [  { id: "tea", name: "Tea", price: 3 },  { id: "cake", name: "Cake", price: 4 },  { id: "lemonade", name: "Lemonade", price: 2 },]; export default function App() {  const [cart, setCart] = useState([]);   function add(product) {    if (cart.some((line) => line.id === product.id)) {      setCart(cart.map((line) => (line.id === product.id ? { ...line, quantity: line.quantity + 1 } : line)));    } else {      setCart([...cart, { ...product, quantity: 1 }]);    }  }   function remove(id) {    setCart(cart.filter((line) => line.id !== id));  }   function sortByName() {    setCart(cart.toSorted((a, b) => a.name.localeCompare(b.name)));  }   const total = cart.reduce((sum, line) => sum + line.price * line.quantity, 0);   return (    <>      {shelf.map((product) => (        <button key={product.id} onClick={() => add(product)}>          Add {product.name}        </button>      ))}      <ul>        {cart.map((line) => (          <li key={line.id}>            {line.name} × {line.quantity} <button onClick={() => remove(line.id)}>Remove</button>          </li>        ))}      </ul>      <p>Total: ${total}</p>      <button onClick={sortByName}>Sort by name</button>    </>  );}
ResultLoading…

add on lines 12 to 18 checks with some whether the product is already in the cart. If it is, line 14 maps over the cart and gives that line a new object with one more in quantity. If not, line 16 adds a new line to the end with spread: the product's fields, plus quantity: 1. Line 21 removes with filter, line 25 sorts a copy by name with toSorted, and line 28 adds up the total from the cart on every render.

Click Add Tea, Add Cake, and Add Tea again: Tea × 2 and Cake × 1, Total: $10. Sort by name puts Cake first. Remove Tea, and the total is $4. Not one line changes cart or an object in it.

Safe for state, or a bug?
  • setItems([...items, "Tea"]);
  • items.push("Tea"); setItems(items);
  • setItems(items.filter((item) => item !== "Milk"));
  • items.splice(0, 1); setItems([...items]);
  • setItems(items.toSorted());
  • setItems(items.sort());
  • setItems([...items].reverse());
  • items[0] = "Tea"; setItems([...items]);
  • const next = [...cart]; next[0].quantity = 2; setCart(next);
  • setCart(cart.map((line, index) => (index === 0 ? { ...line, quantity: 2 } : line)));
Try it yourself
0 of 10 correct

Sort each click handler body: does it build a new array and leave everything in state alone, or does it change the array or an object in it?

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

Common mix-ups

  • "setItems(items.sort()) passes a sorted list." No. It passes the same array, sorted in place. React sees the array it already has and skips the render. Use items.toSorted().
  • "slice and splice are the same." No. slice copies; splice changes the array. toSpliced is the copying splice.
  • "[...items] copies the objects too." No. It copies the array. Its slots point at the same objects, so changing one through the copy changes it in state.
  • "If the page updates, the update was safe." No. Copy the array, then change an object in it, and the page updates while the old state's objects change too.
  • "You can never call sort or push in React." No. On an array you just made, like [...items], they're fine: that's local mutation. The rule is for arrays already in state.
  • "map makes a copy of every object." No. It makes a new array of whatever your function returns. Return the item as it is, and it's the same object.

Practice exercises

Each exercise shows its code's result under it, or when you press Edit & run. Change the code, and the result updates as you type. Try a guess first, then use the hints.

Exercise 1 · Warm-upPredict what reverse leaves behind

What does this code print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const items = ["Milk", "Bread", "Eggs"];
const reversed = items.reverse();
console.log(items.join(", "));

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

    Exercise 2 · Warm-upPredict Remove last with pop

    What does the page show after one click on Remove last? Answer first, then press Edit & run to check.

    import { useState } from "react";
    
    export default function App() {
      const [items, setItems] = useState(["Milk", "Bread", "Eggs"]);
    
      function removeLast() {
        items.pop();
        setItems(items);
      }
    
      return (
        <>
          <p>{items.join(", ")}</p>
          <button onClick={removeLast}>Remove last</button>
        </>
      );
    }

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

      Exercise 3 · PracticeFix the Remove button

      Remove does nothing on the page. Fix remove, then remove Bread. What does the list show?

      import { useState } from "react"; export default function App() {  const [items, setItems] = useState([    { id: 0, name: "Milk" },    { id: 1, name: "Bread" },    { id: 2, name: "Eggs" },  ]);   function remove(index) {    items.splice(index, 1);    setItems(items);  }   return (    <ul>      {items.map((item, index) => (        <li key={item.id}>          {item.name} <button onClick={() => remove(index)}>Remove</button>        </li>      ))}    </ul>  );}
      ResultLoading…

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

        Exercise 4 · PracticeToggle a to-do without changing it

        This toggle works on the page, but it changes a to-do object in state. Rewrite toggle with map. When you tick Buy tea, how many new to-do objects does your version make?

        import { useState } from "react"; export default function App() {  const [todos, setTodos] = useState([    { id: 0, text: "Buy tea", done: false },    { id: 1, text: "Bake a cake", done: false },    { id: 2, text: "Call Asha", done: false },  ]);   function toggle(id) {    const next = [...todos];    const todo = next.find((item) => item.id === id);    todo.done = !todo.done;    setTodos(next);  }   return (    <ul>      {todos.map((todo) => (        <li key={todo.id}>          <label>            <input type="checkbox" checked={todo.done} onChange={() => toggle(todo.id)} /> {todo.text}          </label>        </li>      ))}    </ul>  );}
        ResultLoading…

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

          Exercise 5 · ChallengePlay a song next with toSpliced

          Make Play Rain Song next put Rain Song right after the first song, with toSpliced. After one click, what is second in the queue?

          import { useState } from "react"; export default function App() {  const [queue, setQueue] = useState(["Morning Raga", "Chai Time", "Monsoon"]);   function playNext() {    // Put "Rain Song" right after the first song, with toSpliced.  }   return (    <>      <button onClick={playNext}>Play Rain Song next</button>      <ol>        {queue.map((title) => (          <li key={title}>{title}</li>        ))}      </ol>    </>  );}
          ResultLoading…

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

            Exercise 6 · ChallengeMove an item up with with

            Write moveUp so it swaps an item with the one above it, using with. Click Move up on Tea. What is the order now?

            import { useState } from "react"; export default function App() {  const [items, setItems] = useState(["Cake", "Lemonade", "Tea"]);   function moveUp(index) {    if (index === 0) return;    // Swap items[index] and items[index - 1], without changing items.  }   return (    <ol>      {items.map((item, index) => (        <li key={item}>          {item} <button onClick={() => moveUp(index)}>Move up</button>        </li>      ))}    </ol>  );}
            ResultLoading…

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

              Check your understanding

              For each question, ask: does this make a new array, and does it change the old array or an object in it? Press Edit & run on a question's code to check your answer.

              The Updating arrays in state quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Which call leaves items exactly as it was?

                Choose an answer to see the explanation.

              2. Question 2 of 7After one click on Add eggs, what does this list show?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                import { useState } from "react";
                
                export default function App() {
                  const [items, setItems] = useState(["Milk", "Bread"]);
                
                  return (
                    <>
                      <p>{items.join(", ")}</p>
                      <button
                        onClick={() => {
                          items.push("Eggs");
                          setItems(items);
                        }}
                      >
                        Add eggs
                      </button>
                    </>
                  );
                }

                Choose an answer to see the explanation.

              3. Question 3 of 7What's the difference between slice and splice?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this insertion print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const items = ["Milk", "Bread", "Eggs"];
                const next = [...items.slice(0, 1), "Tea", ...items.slice(1)];
                console.log(next.join(", "));

                Choose an answer to see the explanation.

              5. Question 5 of 7What does this copied list print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const list = [{ name: "Green tea", tried: false }];
                const copy = [...list];
                copy[0].tried = true;
                console.log(list[0].tried);

                Choose an answer to see the explanation.

              6. Question 6 of 7You need to mark the to-do with this id as done. Which update is right?

                Choose an answer to see the explanation.

              7. Question 7 of 7What does with leave in items?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const items = ["Milk", "Bread", "Eggs"];
                const next = items.with(1, "Tea");
                console.log(items.join(", "));

                Choose an answer to see the explanation.

              Key takeaways

              • An array in state is read-only. push, pop, splice, sort, reverse, and items[index] = … change it; don't use them on it.
              • Add with spread, remove with filter, replace or transform with map, and insert with two slices or toSpliced.
              • Sort and reverse a copy: toSorted and toReversed (in every current browser), or [...items].sort().
              • Copying the array doesn't copy its objects. To change an object in a list, map to a new object for that item and keep the rest.
              • Immer works on arrays too: change the draft with any method, and produce builds the new array.

              Remember the one-liner.
              Read the old array, build a new one, and never change an item in place.

              Coming next: Reacting to input with state. It starts the Managing state stage: describe each visual state of a component, and let state choose between them.

              CompleteFrontend Clear concepts. Working examples.