Clone the Object After Manipulation

Immutable objects mean that we can’t change them, however, in JavaScript, that often means a variable is a const. JS, though only enforces immutability on the root reference of a const. This means that while immutability is a concept frameworks like React are built around, it’s really just a promise that the React creators design to, and that end-developers choose to adhere to so that their React app works as expected.

In this article I’ll show you how I bend that rule to make finding and changing a particular object easier.

To simplify the appearance of our code slightly, for the rest of this article, let’s assume that the following function exists.

function deepClone(obj) {
    return JSON.parse( JSON.stringify(obj) )
}

And let’s also assume that the allNodes variable is created at the top of the React function like in the below snippet.

const [allNodes, setAllNodes] = useState([node1, node2, etc…]);

When using useState, React tells us that only when we create a new allNodes and give it to setAllNodes, will a change in allNodes cause React to re-render.

Said another way, we can’t simply change data inside allNodes or react won’t know to rerender, we have actually change the underlying object that allNodes points to. Of course, because it’s a const we can’t do that, so we have to create a new object in its place.

That’s what this code does inside my deleteNode function (Just an example function).

eraseNodeTitle = (node) => {
    let newNodes = deepCopy(allNodes);
    let nodeToChange = getNode(newNodes, node.id);
    nodeToChange.title = "";
    setAllNodes({ newNodes});
 };

The first thing the above code does clones the allNodes variable into a new object. That solves the problem—We can make the modifications to that object and then pass it to the setAllNodes function. Since it’s a different object, we get the expected behaviour.

This is perfectly fine apart from one problem I think we can avoid.

Since the node passed into the function is a reference from inside allNodes, but we need to make changes in newNodes, we have to write the getNode function to iterate through the new nodes and get a reference to the equivalent node inside there.

This works, but it’s a little inefficient and unnecessary—Since we already had a reference to the original node. What if we could just change the original object instead?

We can.

eraseNodeTitle = (node) => {
    node.title = "";
    let newNodes = deepCopy(allNodes);
    setAllNodes({ newNodes});
 };

In the above code, we bend a rule that is often referenced when discussing React. We changed the original object. This is something you shouldn’t do because in order for react to know when to rerender, you need to pass it a new object rather than editing an old one.

The trick here, however, is that we did both. We modified the existing object (cause it was much easier), then we cloned the object to make it new and passed it to React so it re-renders.

It’s worth noting, however, that this could bite you in the butt. React doesn’t rerender straight away, instead, it can take a split second to batch multiple changes at once before rerendering. This means that if you have other code running asynchronously that is referencing the allNodes variable, it will now see your changed data, which may cause unexpected side-effects.

So, should you do this?

I would say that if you’re building quickly and need to get some prototype functionality out the door, sure, but you should go back and clean up too so that this doesn’t create issues for you in the future as your app gets more complex.

That’s it!

I’d love to know if any of these articles helped you.
Share what you’re building or ask me a question on Threads or somewhere else.

Instagram is a great place to see my final creative coding experiments, while the others are are great place to connect or see progress updates.

If my content has helped you, I’d never say no to donations to help me keep writing.

Here are some other things you might like


Author:

Date:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.