tutorial // Jul 16, 2021

How to Modify an Existing Object in a JavaScript Array

A pattern for modifying a specific object in an array using JavaScript.

How to Modify an Existing Object in a JavaScript Array

Getting Started

For this tutorial, we're going to use the CheatCode Next.js Boilerplate to give us a starting point for our work. First, let's clone a copy:

Terminal

git clone https://github.com/cheatcode/nextjs-boilerplate.git

Next, install the dependencies for the boilerplate:

Terminal

cd nextjs-boilerplate && npm install

Finally, start up the development server:

Terminal

npm run dev

With that, we're ready to get started.

Building a React component for testing

To contextualize our work, we're going to build a simple, class-based React component. This will give us a situation where using the pattern we'll learn will make more sense.

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";

class Index extends React.Component {
  state = {};

  render() {
    return <div></div>;
  }
}

Index.propTypes = {};

export default Index;

Here, we're just creating the scaffold for a class component in React. The part we want to pay attention to is the name of the component Index and the path for that file /pages/index.js. Because we're using Next.js, here, we're relying on the Next.js router by placing our component file inside of the framework's /pages directory.

Files and folders here are automatically converted to routes. Because we've placed this in the root of our /pages folder as index.js, this will render at the root URL for our application, or, http://localhost:5000/.

Next, let's take a quick look at that usersFixture file we've imported up top:

/lib/users.js

const users = [
  {
    _id: "f91bbFE72aaDDd8c",
    emailAddress: "Floyd33@hotmail.com",
    name: { first: "Phoebe", last: "Schamberger" },
    address: {
      streetAddress: "39473 David Mill",
      city: "Stammbury",
      state: "Michigan",
      zipCode: "91802",
    },
  },
  {
    _id: "E8c8f6d3fE6761dd",
    emailAddress: "Torey.Konopelski@hotmail.com",
    name: { first: "Orin", last: "Balistreri" },
    address: {
      streetAddress: "27846 Collier Roads",
      city: "Schneiderton",
      state: "Kansas",
      zipCode: "49705-7399",
    },
  },
  {
    _id: "Cd9caEcb4fB1D558",
    emailAddress: "Frederic.Kovacek@yahoo.com",
    name: { first: "Chanelle", last: "Oberbrunner" },
    address: {
      streetAddress: "638 Fadel Cliffs",
      city: "Lake Thorahaven",
      state: "West Virginia",
      zipCode: "12349-0480",
    },
  },
  {
    _id: "BAf1DcEec4b4DBAc",
    emailAddress: "Ottis_Hansen@hotmail.com",
    name: { first: "Briana", last: "White" },
    address: {
      streetAddress: "0540 Brown Meadow",
      city: "Port Jerad",
      state: "Oklahoma",
      zipCode: "14368",
    },
  },
  {
    _id: "1c4E8Aa24c37cBFA",
    emailAddress: "Uriah49@gmail.com",
    name: { first: "Vidal", last: "Stokes" },
    address: {
      streetAddress: "31028 Marquardt Forest",
      city: "North Bethany",
      state: "Indiana",
      zipCode: "32632",
    },
  },
];

export default users;

Here, we have a static list of "users" (these are made up of fake data). Our goal is to load this array of users into our component and then make changes to the objects in the array via JavaScript.

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";

class Index extends React.Component {
  state = {
    users: usersFixture,
  };

  render() {
    const { users } = this.state;

    return (
      <div>
        <header className="page-header">
          <h4>Test</h4>
        </header>
        <div className="responsive-table">
          <table className="table align-middle">
            <thead>
              <tr>
                <th>Name</th>
                <th>Email Address</th>
                <th>Address</th>
                <th />
              </tr>
            </thead>
            <tbody>
              {users.map(({ _id, name, emailAddress, address }) => {
                return (
                  <tr key={_id}>
                    <td>
                      {name?.first} {name?.last}
                    </td>
                    <td>{emailAddress}</td>
                    <td>
                      {address?.streetAddress} {address?.city}, {address?.state}{" "}
                      {address?.zipCode}
                    </td>
                    <td>
                      <button
                        disabled={editingUser}
                        className="btn btn-primary"
                        onClick={() => {
                          this.setState({ editingUser: _id });
                        }}
                      >
                        Edit
                      </button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

Index.propTypes = {};

export default Index;

Back in our component, now, we've taken the usersFixture we imported earlier and are setting on our component's state value as users. Down in the render() function, we've returned some HTML to render out our users list in a table. Here, the CSS class names you see are taken from the Bootstrap CSS framework. The usage of these classes here doesn't impact our actual work—they're just used for presentation.

The part we care about is when we .map() over the users value we placed onto state (again, this is our static array of user objects). Inside of our render() method, we use JavaScript destructuring to "pluck off" users from this.state and then in the returned HTML markup (technically, JSX which is a React-specific language that looks like HTML) we render a <table></table> with our users being listed out in the body.

For the "listing," we keep things simple. Here, we render out a <tr></tr> table row for each user, displaying their name, emailAddress, and physical address. Again, these values are just test data to help us contextualize our work modifying objects in an array.

Finally, for each user, we've added a <button></button> which when clicked will set that user as the editingUser on state. Here, we pass the user's _id (their unique ID in our "database") to say "we're currently editing the user with this _id.

Next, let's wire up that editing process.

Editing a user object in an array

With our base component set, now, let's add in the editing functionality we hinted at above:

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";

class Index extends React.Component {
  state = {
    editingUser: null,
    users: usersFixture,
  };

  renderUserEditor = () => {
    const { editingUser, users } = this.state;
    const user = users.find(({ _id }) => _id === editingUser);

    return (
      <div
        className="edit-user"
        style={{
          border: "1px solid #ddd",
          padding: "20px",
          borderRadius: "3px",
          marginTop: "40px",
          marginBottom: "40px",
        }}
      >
        <form onSubmit={this.handleUpdateUser}>
          <div className="row">
            <div className="col-xs-12 col-sm-3">
              <div className="mb-3">
                <label className="form-label">First Name</label>
                <input
                  type="text"
                  className="form-control"
                  defaultValue={user?.name?.first}
                  name="firstName"
                />
              </div>
            </div>
            <div className="col-xs-12 col-sm-3">
              <div className="mb-3">
                <label className="form-label">Last Name</label>
                <input
                  type="text"
                  className="form-control"
                  defaultValue={user?.name?.last}
                  name="lastName"
                />
              </div>
            </div>
            <div className="col-xs-12 col-sm-6">
              <div className="mb-3">
                <label className="form-label">Email Address</label>
                <input
                  type="text"
                  className="form-control"
                  defaultValue={user?.emailAddress}
                  name="emailAddress"
                />
              </div>
            </div>
          </div>
          <div className="row">
            <div className="col-xs-12 col-sm-5">
              <label className="form-label">Street Address</label>
              <input
                disabled
                type="text"
                className="form-control"
                defaultValue={user?.address?.streetAddress}
                name="streetAddress"
              />
            </div>
            <div className="col-xs-12 col-sm-3">
              <label className="form-label">City</label>
              <input
                disabled
                type="text"
                className="form-control"
                defaultValue={user?.address?.city}
                name="city"
              />
            </div>
            <div className="col-xs-12 col-sm-2">
              <label className="form-label">State</label>
              <input
                disabled
                type="text"
                className="form-control"
                defaultValue={user?.address?.state}
                name="state"
              />
            </div>
            <div className="col-xs-12 col-sm-2">
              <label className="form-label">Zip Code</label>
              <input
                disabled
                type="text"
                className="form-control"
                defaultValue={user?.address?.zipCode}
                name="zipCode"
              />
            </div>
          </div>
          <footer className="mt-4">
            <button type="submit" className="btn btn-success">
              Save
            </button>
            <button
              type="button"
              className="btn btn-default"
              onClick={() => this.setState({ editingUser: null })}
            >
              Cancel
            </button>
          </footer>
        </form>
      </div>
    );
  };

  render() {
    const { editingUser, users } = this.state;

    return (
      <div>
        <header className="page-header">
          <h4>Test</h4>
        </header>
        {editingUser && this.renderUserEditor()}
        <div className="responsive-table">
          <table className="table align-middle">
            <thead>
              <tr>
                <th>Name</th>
                <th>Email Address</th>
                <th>Address</th>
                <th />
              </tr>
            </thead>
            <tbody>
              {users.map(({ _id, name, emailAddress, address }) => { ... })
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

Index.propTypes = {};

export default Index;

Going a bit further, now, we've added editingUser and set it to null on our default state object at that top of our component class. Next, in our render() function, we've added a call to this.renderUserEditor() and have added in the function. The idea here is that, when we click on the "Edit" button for a user, we'll set their _id on state (taken from their user object in the users array) and then simultaneously toggle the rendering of the user editor and disable all of the edit buttons for users until the user editor is closed (either by saving changes or canceling the changes).

Assuming we have an editingUser set and renderUserEditor() has been called, looking at that function, the part we care about is the top:

const { editingUser, users } = this.state;
const user = users.find(({ _id }) => _id === editingUser);

Remember: we're dealing with a static array of users on state. Instead of fetching data from a server, here, we're saying "pluck off the editingUser and users array from state and then use a JavaScript .find() on the users array to find the user who has an _id matching the editingUser we set on to state." So, when we click a user's "Edit" button, they will become the user that we retrieve here.

Once retrieved inside of renderUserEditor(), we render a form that will be responsible for allowing us to make changes to that user. Here, we can see that our form—again, using Bootstrap CSS to clean up our presentation—lists out each of the fields available in the user object as inputs with their defaultValue set to that field's value on the user object. To keep things simple, we're only allowing edits on the name.first, name.last, and emailAddress for the user; the other fields are disabled.

Two more things. First, at the bottom of renderUserEditor(), we return a <footer></footer> with two buttons, a "Save" button and a "Cancel" button. The "Cancel" button here is responsible for clearing out the editingUser on state when it's clicked (remember, this toggles the rendering of the user editor and the disabled state of the edit buttons for users in our list). The more important button, "Save," is set to a type="submit", which means that when clicked it will trigger the onSubmit event for the <form></form> that's wrapping it.

Here, we can see that <form></form> has an onSubmit set to a function this.handleUpdateUser. Let's wire that function up now and see how it plays into modifying our array.

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";

class Index extends React.Component {
  state = {
    editingUser: null,
    users: usersFixture,
  };

  handleUpdateUser = (event) => {
    event.preventDefault();

    const { editingUser, users } = this.state;
    const updatedUsers = [...users];
    let userToUpdate = updatedUsers.find(({ _id }) => _id === editingUser);

    if (userToUpdate) {
      userToUpdate.name = {
        first: event.target.firstName.value,
        last: event.target.lastName.value,
      };

      userToUpdate.emailAddress = event.target.emailAddress.value;
    }

    this.setState({ users: updatedUsers, editingUser: null });
  };

  renderUserEditor = () => {
    const { editingUser, users } = this.state;
    const user = users.find(({ _id }) => _id === editingUser);

    return (
      <div
        className="edit-user"
        style={{
          border: "1px solid #ddd",
          padding: "20px",
          borderRadius: "3px",
          marginTop: "40px",
          marginBottom: "40px",
        }}
      >
        <form onSubmit={this.handleUpdateUser}>
          ...
        </form>
      </div>
    );
  };

  render() {
    const { editingUser, users } = this.state;

    return (
      <div>
        <header className="page-header">
          <h4>Test</h4>
        </header>
        {editingUser && this.renderUserEditor()}
        <div className="responsive-table">
          <table className="table align-middle">
            <thead>
              <tr>
                <th>Name</th>
                <th>Email Address</th>
                <th>Address</th>
                <th />
              </tr>
            </thead>
            <tbody>
              {users.map(({ _id, name, emailAddress, address }) => { ... })}
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

Index.propTypes = {};

export default Index;

Inside of our new handleUpdateUser() function, first, we take in the submit event as an argument and immediately call to its .preventDefault() method. This is important as we don't want our submit event to trigger a browser refresh—this stops it.

Next, we see something similar to what we saw in renderUserEditor(). This is the part we care about in this tutorial. Here, we're plucking off the editingUser and users array from this.state again. Remember, our goal is to edit an object that exists in an array. In order to do that, we need to know two things:

  1. What array are we looking in?
  2. How do we find the object to update in that array?

Here, we'll use the _id we set on editingUser when we clicked the "Edit" button next to one of our users. Now, again, we need to stress that our goal is to edit an object as it exists in an array. In this example, the array is our users array.

First, before we "find" our user, we create a copy of the users array on state (it's beyond the scope of this tutorial but the golden rule in React is that you don't want to mutate state values directly) with [...users]. Here, in one line we're saying "create a new array [] and then use the ... spread operator to "unpack" or copy the contents of users into that new array." This is the array that we'll modify.

Next, again using a JavaScript .find() on our new updatedUsers array, we run the same test we used earlier to say "find us a user with an _id that matches editingUser." Assuming that we do find that user, we start to make changes to them. Here, we're setting the name object and the emailAddress field on the userToUpdate.

Though it may not look like it, because we used a JavaScript .find() here, we're actually modifying the matching userToUpdate object as it exists in the updatedUsers array in memory. What this means is that even though our code is making changes to userToUpdate, ultimately, it's making changes to updatedUsers.

Once these changes are complete (here, we're just setting the aforementioned values to the corresponding inputs in our form using event.target.<fieldName>.value where <fieldName> is the name="" attribute on the input), we overwrite our users value on state with this.setState(), passing our updatedUsers array.

The end result? We'll see our users list update in our table, successfully demonstrating that we updated an object inside of an array.

Wrapping Up

In this tutorial, we learned how to modify an object in a JavaScript array. To contextualize our work, we built a React component that modified a list of users in an array on state and then set it back on state, rendering the updated list in an HTML table.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode