Updating arrays in state requires creating a new array
Arrays are mutable JavaScript objects that should be treated as read-only in React state. When you want to update an array in state, you must create a new array (or copy an existing one) and then set state to use the new array. Do not directly mutate array elements.
Using map to update array elements
To update a specific element in an array stored in state, use the array map method to create a new array. When you find the element to update, return a new object with the updated property using spread syntax. For elements that don't need updating, return them unchanged. Example: setList(list.map(artwork => artwork.id === artworkId ? { ...artwork, seen: nextSeen } : artwork))
Using Immer library to simplify array updates
The Immer library simplifies array updates in state. With useImmer hook, you can write code that looks like mutation but produces immutable updates. For example: updateList(draft => { const artwork = draft.find(a => a.id === artworkId); artwork.seen = nextSeen; }) instead of manually creating new arrays and objects.
Use map() to render lists from arrays
You can use JavaScript's filter() and map() with React to filter and transform your array of data into an array of components. Use map() to transform each item in an array into a component.
Keys help React track list items
For each array item in a list, you must specify a key. Usually, you will want to use an ID from the database as a key. Keys let React keep track of each item's place in the list even if the list changes.
List key attribute requirements
For each item in a list, you must pass a `key` attribute with a string or number that uniquely identifies that item among its siblings. Usually, a key should come from your data, such as a database ID. React uses keys to know what happened if you later insert, delete, or reorder items.
Render lists using map() function
To render lists of components in React, you use JavaScript features like the `map()` function to transform an array into an array of JSX elements. Each item in a list should have a `key` attribute with a value that uniquely identifies that item among its siblings.
Spread syntax creates shallow copy of arrays
The spread syntax (...) in JavaScript arrays creates a new array with the enumerated items. For example, [...history, nextSquares] creates a new array containing all items from history followed by nextSquares. This is useful for immutably appending to arrays.
Use slice() to create array copies instead of mutating
To avoid mutating arrays directly, use the slice() method to create a copy. For example, const nextSquares = squares.slice() creates a copy that you can then modify. This is important for React because it allows state management patterns and optimizations like time travel features.
Shallow copying arrays does not prevent nested mutations
When you copy an array using the spread operator like `[...list]`, the copy is shallow. The new array contains the same item objects as the original. If you mutate an object inside the copied array directly, you are still mutating the state. For example, `const nextList = [...list]; nextList[0].seen = true;` mutates the original `list[0]` because both arrays point to the same object.
Updating nested objects in arrays requires copying from mutation point to top level
When updating nested state, you need to create copies from the point where you want to update all the way up to the top level. To update an object inside an array, use `map` to create a new array and the object spread syntax to create a new object. For example: `setList(list.map(item => item.id === targetId ? {...item, seen: nextSeen} : item))`.
Treat arrays in state as immutable
Arrays are mutable in JavaScript, but you should treat them as immutable when storing them in React state. You should not reassign items inside an array like `arr[0] = 'bird'`, and you should not use mutating methods such as `push()` and `pop()`. Instead, create a new array by calling non-mutating methods like `filter()` and `map()`, then set state to the resulting new array.
Immer library for concise array state updates
Immer is a library that lets you write state update logic using convenient mutating syntax while it handles producing the copies automatically. With Immer's `useImmer` hook, you can mutate a `draft` object and Immer will construct the next state from your changes. For example: `updateMyList(draft => { draft[0].seen = true; })`. This is equivalent to non-mutating approaches but more concise.
Using useImmer to update nested objects in arrays
With Immer's `useImmer` hook, you can write mutations on a draft object: `const [myList, updateMyList] = useImmer(initialList); updateMyList(draft => { const artwork = draft.find(a => a.id === id); artwork.seen = nextSeen; });`.
Using map to update nested objects in arrays
To safely update an object inside an array in state, use `map` with the object spread syntax to create both a new array and new object: `setMyList(myList.map(artwork => artwork.id === artworkId ? {...artwork, seen: nextSeen} : artwork))`.
Array mutation methods to avoid and prefer in React state
When dealing with arrays inside React state, avoid methods that mutate the array and use non-mutating alternatives instead. For adding: avoid `push` and `unshift`, prefer `concat` or `[...arr]` spread syntax. For removing: avoid `pop`, `shift`, `splice`, prefer `filter` or `slice`. For replacing: avoid `splice` or `arr[i] = ...` assignment, prefer `map`. For sorting: avoid `reverse` and `sort`, copy the array first then call the mutating method.
slice vs splice confusion and correct usage
`slice` and `splice` are named similarly but are very different. `slice` lets you copy an array or a part of it without mutating the original. `splice` mutates the array to insert or delete items. In React state, you will use `slice` (no 'p') much more often because you should not mutate objects or arrays in state.
Adding items to arrays in state with spread syntax
To add items to an array in state, use the spread operator syntax. To add at the end: `setArtists([...artists, { id: nextId++, name: name }])`. To add at the beginning: `setArtists([{ id: nextId++, name: name }, ...artists])`.
Removing items from arrays in state with filter
To remove an item from an array in state, use the `filter` method to create a new array that excludes the item. For example: `setArtists(artists.filter(a => a.id !== artist.id))`. The filter method does not modify the original array.
Transforming array items in state with map
To change some or all items in an array in state, use the `map()` method to create a new array. The function passed to `map` receives each item and can decide what to do with it based on its data or index. For example: `const nextShapes = shapes.map(shape => shape.type === 'square' ? shape : {...shape, y: shape.y + 50})`.
Replacing array items in state by index with map
To replace items in an array at specific indices, use `map` and receive the item index as the second argument. For example: `const nextCounters = counters.map((c, i) => i === index ? c + 1 : c)`.
Inserting items at specific positions in arrays with slice and spread
To insert an item at a specific position in an array, use the spread operator combined with the `slice()` method. For example, to insert at index 1: `const nextArtists = [...artists.slice(0, 1), { id: nextId++, name: name }, ...artists.slice(1)]`. This creates an array with items before the insertion point, the new item, and items after the insertion point.
Reversing or sorting arrays in state
To reverse or sort an array in state, copy the array first using the spread operator, then call the mutating method on the copy. For example: `const nextList = [...list]; nextList.reverse(); setList(nextList);`. This works because you are mutating a copy, not the original state.