DEV Community

Cover image for How to Easily update an Object that's in state in React
Noel Ethan Chiwamba
Noel Ethan Chiwamba

Posted on • Originally published at dev.to

How to Easily update an Object that's in state in React

Contents


Introduction

One of the fundamental concepts of the React framework is state management. State in react can hold any kind of JavaScript data types ranging from the primitive data types like string, number and boolean to the complex data types like objects.

There are many ways to introduce state into your component, one of the many ways is using the hook: useState. Typically, the useState hook allows us to initialize the state variable.

const newStudent = {
name:'Jane Doe',
age: 15
}
const [student, setStudent] = useState(newStudent);
Enter fullscreen mode Exit fullscreen mode

On the other hand, an Object in JavaScript is a real world entity that contains properties and or type. An example of an object can be as put in the above code; a student with properties name, age.

Further we can also have an array of objects which is fairly common in many applications

//an array of song objects with properties like title and votes
const songList = [
  {
    id: 0,
    title: "\"All for you\","
    votes: Math.floor(Math.random() * 5 + 1),
  },
  {
    id: 1,
    title: "\"Always win\","
    votes: Math.floor(Math.random() * 5 + 1),
  },
  {
    id: 2,
    title: "\"Grateful\","
    votes: Math.floor(Math.random() * 6 + 2),
  },
];
Enter fullscreen mode Exit fullscreen mode

The song list array is comprised of objects and each object is made up of 3 properties namely; id, title and votes.

We are using Math.Random() function to prepopulate the votes property that is in our song object, this will help us to have initial votes on the first render. You can read more about Math.random() here

Updating object that's in state

Just as any other state in react, an object that's in state is also subject to change, however there are precautions or standards that must be met when updating an object that's in state. In this article I'll help you to update an object in state in a more clean way.

For example for us to make the above songList array of objects editable or updatable we need to put it in state inside a component.

  const [songs, setSongs] = useState(songList);
Enter fullscreen mode Exit fullscreen mode

if we can provide a user interface for the array of objects above and traverse the songList array that is above so that a user should be able to vote for a song that he wishes it may look like this.

return (
      <ul className="song-list">
        //the map array function will help us to traverse through the songs array
        {songs.map((song) => (
          <li key={song.id} className="song-item">
            <p className=""><span className="votes">{song.votes}</span> votes</p>
            <div className="title">{song.title}</div>
            <div className="caret">
              <BiCaretUp size={24} />
              <span className="text">Vote</span>
            </div>
          </li>
        ))}
      </ul>
  );};
Enter fullscreen mode Exit fullscreen mode

and suppose after styling the output looks like this.

A Simple UI that allows a user to vote for countless times for a song  he likes

This simple UI will allow any user to click on the preferred song and vote for it countless times ( not applicable in real world applications ).

Creating an Up vote function

For us to update the vote after user clicks on the vote button (caret) we need to define a function that will do this work for us. So lets define a function onVote() that will be called whenever a user clicks the vote caret.

The function will receive a song ID for the clicked song and will

const onVote=(id)=>{
   //code for updating the votes the chosen song
}
Enter fullscreen mode Exit fullscreen mode

and then we will add an onClick() attribute on our button in our UI to capture any clicks on our vote button

...
// adding the onClick function 
<div 
  onClick={()=>onVote(id)} 
  className="caret">
     <BiCaretUp size={24} />
     <span className="text">Vote</span>
 </div>
....
Enter fullscreen mode Exit fullscreen mode

Whenever a user clicks on this div an the song ID will be passed to this onVote().

Implementing The Update Votes Button

There are many ways how we can update the votes after user clicks the vote button. But one thing we have to do is avoid something called mutation.

The major concept to grab when updating object in state is that: An object once created, it never changes , and its variable should never be reassigned. The term mutation refers to
the situation that a value is changed in-place updating a value stored somewhere in memory. In React, state is considered to be an immutable object, meaning that you should not directly change the state object.

Showcasing Mutation

For example, to implement the onVote() function one can do this just to update the votes in a single specified object.

const onVote = (id) => {
    songs.map((song) => {
      if (song.id === id) {
      //incrementing the votes of the specified song after user clicks
         song.votes = song.votes+1
        }
    });
  };
Enter fullscreen mode Exit fullscreen mode

The situation above portrays the concept of mutation. We are directly accessing the song.votes and incrementing or in other words changing it directly and while its in memory. Respectively this will not work or it may either bring an error. However, there is a specific way to do this without complications.

One Command for Updating Object in state

There is only one rule that works and never fails whenever you want to update object in state.

Thou shall copy first and then modify

What this means is that, to avoid mutation You have to create a new Object that will take the contents of the object that wants to be modified and modify the field that you want. In other words whenever you want to update an object in state copy the contents of the object on a new one and then update the object and the update state.

This may look something like this:

const onVote = (id) => {
    const newSongs = songs.map((song) => {
      if (song.id === id) {
         const nextSong = {
            id:song.id,
            title: "song.title ,"
            votes:song.votes+1
        }
        return nextSong
      } else {
        return song;
      }
    });
    setSongs(newSongs);
  };

Enter fullscreen mode Exit fullscreen mode

First, we use map() to traverse the songs array. Importantly, map() returns a new array as opposed to modifying the array song.

Next, as we traverse we check if the current song ID matches songID. If it does, we create a new object, copying over the properties from the original product object. We then overwrite the votes property on our new product object. We set it to the incremented vote count. So easy.

a UI showing how the voting update is occuring

Her we can see using the above GIF that the votes are incrementing easily after we. REMEMBER! we are only modifying the votes property in our state. You can update many fields depending on the type of application you are developing.


Simple Way To Copy JavaScript object For easy Property update

As we have seen in the recent section that you first of all have to copy the object into whole new object,


   const newSongs = songs.map((song) => {
      if (song.id === id) {
         const nextSong = {
         //copying the song object values unto a new object 
            id:song.id,
            title:song.title,
            votes:song.votes+1
        }
Enter fullscreen mode Exit fullscreen mode

hoever this can become so tiresome if the object can be so big that is when it has so any properties with it. suppose the object looks like this:


const song ={
    id: 0,
    title: "All for you",
    votes: Math.floor(Math.random() * 5 + 1),
    duration: "5 min",
    type: "mp3",
    size: '5MB',
    available_online:true,
    artist:{
        name:'Jones Boy',
        adress:"300 longstreet ",
    }
  },

Enter fullscreen mode Exit fullscreen mode

As you can see it has so many properties, and to copy them just as we did above by doing something like title = song.title can be so tiresome.

Here is where the Object.assign() function comes in.

This function is used places where we want to create a
modified version of an existing object.

  const onVote = (id) => {
    const newSongs = songs.map((song) => {
      if (song.id === id) {
        return Object.assign({}, song, {
          votes: song.votes + 1,
        });
      } else {
        return song;
      }
    });
    setSongs(newSongs);
  };
Enter fullscreen mode Exit fullscreen mode


`

We are passing three arguments to the function. The first argument is a new JavaScript object, the one that Object.assign() will basically return. The second is the object whose properties we’d like to build off of. The last is the changes we’d like to apply.

So there is no need to assign all those properties one by one to the new object.

Conclusion

This article has clearly explained how to modify and update an object that's in state without mutation. It has also elaborated on the JavaScript function (Object.assign()) that easily copies an existing JavaScript object into a new one and returns a modified object.

Oldest comments (4)

Collapse
 
vishal8888a8 profile image
vishal giri

Good one but I think spread operator is the best way to update react state in such case

Collapse
 
noel_ethan profile image
Noel Ethan Chiwamba

I understand there are a couple of ways to achieve the same, I just wanted to make it basic. Thank You.

Collapse
 
tealover418 profile image
Stijn de Ligt

You should look into useReducer. It's an alternative to useState but for more complex objects like what you have here.

Collapse
 
noel_ethan profile image
Noel Ethan Chiwamba

Thank you. However I just wanted to keep it basic, but i will consider useReducer too as a Scale up for the article.