Mutating Values in Arrays in React

This is a blog post I wrote Jan 7, 2023, just wanted to have it in ATProto instead of stored on my own website.

Currently using Preact to update one of my projects and i realized i came across something that was annoying that i didn’t encounter in Vue (my preferred framework), arrays need to be copied entirely on every kind of mutation because react tracks references to object.

This is especially frustrating if you have an array of things that are each supposed to be reactive and changeable using controls on the page.

function App() {
const [arr, setArr] = React.useState([false, false, false]);
function modifyArray(index, value) {
const copy = arr.slice()
copy[index] = value
setArr(copy)
}
return
{arr.map((v, i) =>
modifyArray(i, !v)}>Toggle
{v ? "Yes!" : "No."}
)}

}

As demonstrated, each item has a button that, when clicked, updates the item at the specified index using the modifyArray function. this code makes a copy of the array, sets a value in the copy, and then changes the state of the arr variable. you end up having to do this individually for each array, unless there’s a helper of some kind i’m like totally missing (which sounds exactly like me to miss…)

using __proto__ to bypass copying

There’s something very cursed here we can do to not have to copy the array on every change though, it’s possible to make an object with a new reference that refers to the same object without having to create a copy. by setting the __proto__ field in a new object, this new object has its own reference but still inherits everything from the original object, including the stored data. this means we can modify the array without having to make a copy.

function modifyArray(index, value) {
arr[index] = value
setArr({__proto__: arr})
}

And according to this benchmark:

JSBEN.CH - JavaScript Performance Benchmarking Playground

Free online JavaScript benchmark tool. Compare code snippets, measure performance, and find the fastest solution for your JavaScript code.

It’s technically faster to make an object by making a new object and setting __proto__.

one known issue with creating a new object with __proto__ set to the array is that the criteria that signifies that it is an array is lost, so methods like Array.isArray don’t work anymore. there may be other inconsistencies that arise from using this technique, so it’s probably best just to use array copying unless you KNOW what you’re doing.

i don’t think i’m ever going to use it, but i think it’s neat to know its existence.

we can make this easier for ourselves (really really evil)

Something that I could do (but I'd rather not just use React) is use proxies.

here’s a helper that i made that you can use to modify arrays and update the state simultaneously without having to constantly repeat the boilerplate for array mutation every single time.

function useArray([arr, setArr]) {
return new Proxy(arr, {
set(target, prop, value) {
const copy = target.slice()
copy[prop] = value
setArr(copy)
}
})
}

using this new useArray helper, we can wrap useState in arrays and every mutation that happens to a value in the array automatically updates the state in the component. of course, this doesn’t work with other actions, like changing the length, pushing, shifting, you need to set the state of the array manually for those actions. this is a very specific proxy that helps with my specific use-case, which is to mutate values that already exist in an array. it is possible, however, to be able to implement those things in a proxy, but i won’t…

this helper allows us to reduce the code we had earlier using the modifyArray function and remove it and replace it with something that is smaller and can be reused whenever we have this same pattern:

function App() {
const arr = useArray(React.useState([false, false, false]))
return
{arr.map((v, i) =>
arr[i] = !v}>Toggle
{v ? "Yes!" : "No."}
)}

}

finally, getting the features i want without having to do that awful boilerplate… and i can keep reusing this for all arrays that this pattern fits. of course, i think it being non-obvious behaviour is enough reason to be disgusted w.

I love the way Vue does this way more

so as a sort of update, i decided to actually just convert the entire project to vue (which took like less than an hour for me) and vue handles these kinds of situations so much better.

i recreated the example that i made for demonstrating mutating values in preact here:




Toggle!
Yes.
No!




Vue.createApp({
data() {
return {
arr: [false, false, false]
}
}
}).mount("#app")

vue actually uses proxies in order to do the same thing that i did in my react example, which is to capture a change to something reactive in the array and then update the state.

of course, a better way to actually make controls like these that play nicely with vue is to use form controls that have the v-model interface for what we want to express. in this case, those would be checkboxes.



unfortunately, we need to refer to some kind of reference instead of a value (which makes sense) so we can’t just plop v in v-model. however, you are able to update properties of an object since you are able to resolve a reference from a property access.

just need to add the following to our data function:

arr2: [{x: false}, {x: false}, {x: false}]

add this to our template:




Yes.
No!



and we can keep modifying properties of an object that are in an array like this. i basically used this all over my personal project and it was nice.