Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Represent Nested Objects in a JSON Array with React.js Components

Let's take a look at the classic todo app but with a twist: a separate TodoItem component that handles the functionality for each element in the array.

Oct 10, 2020 • 9 Minute Read

Introduction

When dealing with an array of items in a React.js component, it is best practice to create an independent component that handles each element of the array. In this guide, we'll take a look at the classic todo app in React.js but give it a twist by creating a separate TodoItem component whose responsibility is to handle the functionality for each element in the array. This means that each element of the array will correspond to a TodoItem component that will be mounted within TodoList.

Setup

Start off by creating the parent component TodoList:

      import React from 'react';

export default class TodoList extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      items: []
    }
  }

  render() {
    return (
      <div>
        <h1>Todo List</h1>
        <hr/>
        <button>
          Add Item
        </button>
      </div>
    );
  }
}
    

Notice that the component maintains an array of items in its state. To simplify things, the items array will just contain string elements.

Next, create the TodoItem component that will serve as a container for each element of items. Initially, it would look like the following:

      import React from 'react';

export defualt class TodoItem extends React.Component {
  constructor(props) {
    super(props);

    this.state = {}
  }

  updateItem(event) {
  }

  handleClick() {
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.props.item}
          onChange={this.updateItem.bind(this)}
        />
        <button onClick={this.handleClick.bind(this)}>
          Delete
        </button>
      </div>
    );
  }
}
    

You don't need to maintain any state here. The component has its own input element whose value is an item passed by its props. Whenever the item is modified, it invokes the updateItem method. It also maintains its own delete button that invokes a handleClick method when clicked.

Logic for Item Management

Next, add the necessary methods within TodoList that deals with adding and deleting an item.

Adding an Item

      handleAddClicked() {
  var items = this.state.items;

  items.push("");

  this.setState({
    items: items
  });
}
    

This method is pretty straightforward. It extracts the current items array and pushes in a blank string before updating the state of items. Bind the method to the button for adding an item like so:

      <button
  onClick={this.handleAddClicked.bind(this)}
>
  Add Item
</button>
    

Deleting an Item

Next, create a method for deleting an item:

      deleteItem(index) {
  var items = this.state.items;

  items.splice(index, 1);

  this.setState({
    items: items
  });
}
    

Since there is no unique identifier for an item element, you will make use of index information to know which element of the array to delete. This is done by passing it to the splice method of an array and having 1 as the second argument, making it remove a single item based on the index. The approach is similar to the previous method. Extract the current items state, remove based on index, and update the items state. Notice also that you do not call this method anywhere explicitly within TodoList. Instead, this will be passed as a reference method to each TodoItem later on.

Updating an Item

Next, create a method for updating an item:

      updateItem(item, index) {
  var items = this.state.items;

  items[index] = item;

  this.setState({
    items: items
  });
}
    

The logic follows the same pattern as delete. The difference is now you have the newly updated item as the first argument to be loaded to the array of items based on index.

Passing Functionality to TodoItem

Before you attempt to load TodoItems for each element of items array, make sure to import the component accordingly:

      import TodoItem from './TodoItem';
    

Create a function that returns a mapped set of TodoItems within TodoList called renderItems:

      renderItems() {
  var context = this;

  return (
    this.state.items.map(function(o, i) {
      return  <TodoItem
                key={"item-" + i}
                item={o}
                index={i}
                updateItem={context.updateItem.bind(context)}
                deleteItem={context.deleteItem.bind(context)}
              />
    })
  );
}
    

Notice that the first thing you do is to create a reference to this using a variable context. This is useful when you want to refer to the instance of TodoList within deeply nested JavaScript code, such as the one presented in return. The trick of this method is to iterate through items and map it to an instance of TodoItem by passing in the following:

  1. key: The unique identifier for this component
  2. item: The actual element per loop in the array
  3. index: The index of the element
  4. updateItem: A reference to TodoList's updateItem method that can be invoked by TodoItem
  5. deleteItem: A reference to TodoList's deleteItem method that can be invoked by TodoItem

All of these are accessible by each TodoItem's props.

To render the components, invoke renderItems within the render method of TodoList:

      {this.renderItems()}
    

Logic Methods for TodoItem

Finally, add some functionality to your TodoItem component for updating and deleting an item:

      updateItem(event) {
  this.props.updateItem(event.target.value, this.props.index);
}
    

This method invokes the updateItem of its parent via props. Notice that the value for the first argument (the newly updated item) is taken from the input element of this TodoItem. The second argument will be the index passed to it from props as well.

      handleClick() {
  this.props.deleteItem(this.props.index);
}
    

Delete works the same way. Invoke the deleteItem of the parent by accessing it from this TodoItem's props. Pass in the index to be deleted, which is also accessible via props.

Hook up the logic to TodoItem's interface:

      <input
  type="text"
  value={this.props.item}
  onChange={this.updateItem.bind(this)}
/>
<button onClick={this.handleClick.bind(this)}>
  Delete
</button>
    

Overall Code

TodoItem

      import React from 'react';

export defualt class TodoItem extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      item: props.item
    }
  }

  updateItem(event) {
    this.props.updateItem(event.target.value, this.props.index);
  }

  handleClick() {
    this.props.deleteItem(this.props.index);
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.props.item}
          onChange={this.updateItem.bind(this)}
        />
        <button onClick={this.handleClick.bind(this)}>
          Delete
        </button>
      </div>
    );
  }
}
    

TodoList

      import React from 'react';
import TodoItem from './TodoItem';

export default class TodoList extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      items: []
    }
  }

  handleAddClicked() {
    var items = this.state.items;

    items.push("");

    this.setState({
      items: items
    });
  }

  updateItem(item, index) {
    var items = this.state.items;

    items[index] = item;

    this.setState({
      items: items
    });
  }

  deleteItem(index) {
    var items = this.state.items;

    items.splice(index, 1);

    this.setState({
      items: items
    });
  }

  renderItems() {
    var context = this;

    return (
      this.state.items.map(function(o, i) {
        return  <TodoItem
                  key={"item-" + i}
                  item={o}
                  item={i}
                  updateItem={context.updateItem.bind(context)}
                  deleteItem={context.deleteItem.bind(context)}
                />
      })
    );
  }

  render() {
    return (
      <div>
        <h1>Todo List</h1>
        {this.renderItems()}
        <hr/>
        <button
          onClick={this.handleAddClicked.bind(this)}
        >
          Add Item
        </button>
      </div>
    );
  }
}
    

Conclusion

This may be a slightly more advanced todo app, but it allows you as a developer to be more modular with the way the app handles and modifies information. In fact, it's considered good practice to create separate components for managing individual pieces of information such as elements of an array. Although we simply modified string elements in this example, this can be extended to have nested components deal with more complex objects.

For any questions or concerns, or if you simply want to chat about programming in general, hit me up @happyalampay!