Skip to content

Commit 61c5422

Browse files
authored
docs: add how to update values inside arrays (#1193)
* docs: add how to update values inside arrays * docs: simplify array update guide with "Why this works" * docs: clarify usage of proxy object in array update example
1 parent 6d34cd6 commit 61c5422

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
title: 'How to update values inside arrays'
3+
---
4+
5+
# How to update values inside arrays
6+
7+
Avoid unnecessary re-renders when updating values inside arrays.
8+
9+
## Basic approach (triggers full list re-render)
10+
11+
In this example, iterating over the snapshot items causes the entire list to re-render whenever any item changes.
12+
13+
```js
14+
import { proxy, useSnapshot } from 'valtio'
15+
16+
const state = proxy({
17+
title: 'My Counter list',
18+
items: [
19+
{ id: 1, count: 0 },
20+
{ id: 2, count: 0 },
21+
],
22+
})
23+
24+
function Counter({ item }) {
25+
return (
26+
<div>
27+
<span>{item.count}</span>
28+
<button onClick={() => item.count++}>+1</button>
29+
</div>
30+
)
31+
}
32+
33+
function CounterList() {
34+
const snap = useSnapshot(state)
35+
return (
36+
<div>
37+
<h1>{snap.title}</h1>
38+
{/* Touching snap.items causes the whole list to re-render when any child updates */}
39+
{snap.items.map((item) => (
40+
<Counter key={item.id} item={item} />
41+
))}
42+
</div>
43+
)
44+
}
45+
```
46+
47+
## Optimized approach (only changed items re-render)
48+
49+
Pass the proxy item (not snapshot) to children and let each child subscribe to its own item.
50+
Do not access the entire array from the snapshot in the parent component.
51+
52+
```js
53+
import { proxy, useSnapshot } from 'valtio'
54+
55+
const state = proxy({
56+
title: 'My Counter list',
57+
items: [
58+
{ id: 1, count: 0 },
59+
{ id: 2, count: 0 },
60+
],
61+
})
62+
63+
function Counter({ item }) {
64+
const snap = useSnapshot(item)
65+
return (
66+
<div>
67+
<span>{snap.count}</span>
68+
<button onClick={() => item.count++}>+1</button>
69+
</div>
70+
)
71+
}
72+
73+
function CounterList() {
74+
const snap = useSnapshot(state)
75+
return (
76+
<div>
77+
<h1>{snap.title}</h1>
78+
{/* Only the length is accessed from snap, so only updated children re-render */}
79+
{Array.from({ length: snap.items.length }, (_, index) => (
80+
{/* Note that we are passing the proxy object (state) instead of the snapshot (snap) to the child component */}
81+
<Counter key={state.items[index].id} item={state.items[index]} />
82+
))}
83+
</div>
84+
)
85+
}
86+
```
87+
88+
## Why this works
89+
90+
Because the proxy (in this case: `state.items[index]`) is a proxy object instead of a snapshot object, accessing it does not track usage. This prevents the parent component from "subscribing" to the item's internal changes, effectively isolating updates to the child component.
91+
92+
When you call `snap.items.map()`, you access every item in the array, causing Valtio to re-render whenever _any_ item changes. By only accessing `snap.items.length`, Valtio only re-renders when the array length changes. Using `Array.from({ length: n }, (_, i) => ...)` creates an array by index without touching snapshot items, letting you access the proxy directly.

website/lib/mdx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ export function getDocsNav(): NavigationTree {
270270
pages['how-to-avoid-rerenders-manually'],
271271
pages['how-to-easily-access-the-state-from-anywhere-in-the-application'],
272272
pages['how-to-organize-actions'],
273+
pages['how-to-update-values-inside-arrays'],
273274
pages['how-to-persist-states'],
274275
pages['how-to-reset-state'],
275276
pages['how-to-split-and-compose-states'],

0 commit comments

Comments
 (0)