Exercise: Throw It Away

Exercise 8 Easy

Prerequisites for the exercise

  1. React State

Objective

Create a RemovableItemList component to render a list of removable items.

Description

Let's say we have a list of given items, wherein each item has a 'Remove' button next to it as illustrated below:

  • Python
  • JavaScript
  • PHP
  • Dart

The button, as its name suggests, serves to remove the corresponding item from the list completely.

For example, if we click the button on the first item, here's the list we'd get:

  • JavaScript
  • PHP
  • Dart

We can imagine that the button actually reads "Throw this item away," but we won't use the text itself because it's a little bit rude. (Do you think it is?)

In this exercise, you have to create a RemovableItemList component to render such a list.

Here are a couple of points to note regarding the implementation of RemovableItemList:

  • The list of items should be provided to the component via an items prop, in the form of an array.
  • To update the state of the component, you must only use an array method that returns a new array; NOT one which mutates the original array.

Shown below is a dummy usage of this component:

function App() {
   return (
      <RemovableItemList items={[
         'Python',
         'JavaScript',
         'PHP',
         'Dart'
      ]}/>
   );
}

Here's the program you should get in the end:

Live Example

View Solution

New file

Inside the directory you created for this course on React, create a new folder called Exercise-8-Throw-It-Away and put the .js solution files for this exercise within it.

Solution

Before we begin, first let's spare a few minutes understanding what exactly do we need to create, and only then proceed to actually creating it.

We need to define a component called RemovableItemList that renders a <ul> list containing a series of <li> elements, each of which could be removed by virtue of a button inside it.

Now since the button causes a UI change, it's apparent that it's meant to trigger a state update. But where?

Well, if we think for a second, we see that it makes perfect sense to give a state to RemovableItemList, indicating the current list of items rendered in it. Initially, this state would be set to the given items array but as we interact with the 'Remove' buttons, it would keep on reducing down until it becomes empty.

Let's call this state as items.

You might be wondering how come there is a prop and state with the exact same name. Well, indeed, they can't be of the same name! We'll name the prop variable inside the component differently than what we name it outside the component. More details are laid out in the discussion below.

Each button's click handler would serve to modify this items state. And that's by removing the corresponding item from items and then providing the new array (with the item removed) to the state update function.

And that's essentially it.

Let's get to code all this beautiful logic.

First things first, let's define the basic structure of RemovableItemList:

import React, { useState } from 'react';

function RemovableItemList({ items: initialItems }) {
   const [items, setItems] = useState(initialItems);
   return (
      <ul>
         {items.map((item, i) => (
            <li key={i}>{item} <button>Remove</button></li>
         ))}
      </ul>
   );
}

Notice the way we destructure the given props argument, extracting the prop items into a variable called initialItems. We do this because we want to create a state value called items later on in the function; it's impossible to do so if there is a similarly-named prop.

Also notice the application of key to each <li> element. This is necessary because React requires the key prop when we render lists. Besides this, since there is no need of real uniqueness right now, we're good to go with the index of each list item as the key.

With this code in place, let's now set up a click handler on each button:

function RemovableItemList({ items: initialItems }) {
   const [items, setItems] = useState(initialItems);
   return (
      <ul>
         {items.map((item, i) => (
            <li key={i}>{item} <button onClick={() => removeItem(i)}>Remove</button></li>
         ))}
      </ul>
   );
}

When a button is clicked, it removes the corresponding item from the items array by virtue of a function removeItem().

This removeItem() function is defined as follows:

function RemovableItemList({ items: initialItems }) {
   const [items, setItems] = useState(initialItems);

function removeItem(index) {
setItems(items.filter((_, i) => i !== index));
} return ( <ul> {items.map((item, i) => ( <li key={i}>{item} <button onClick={() => removeItem(i)}>Remove</button></li> ))} </ul> ); }

As required by the exercise, we aren't allowed to use anything besides an array method (for e.g. a for loop, a while loop, or any other imperative code) to perform the removal of an item.

Plus, we can't even use an array method that mutates the original array. We're only allowed to use a method that returns a new array.

And for this, the perfect choice is the filter() method.

These requirements second the architecture of React

Note that these requirements aren't impractical — they really second the functional architecture of React, whereby functions don't perform side effects and return new values after processing given values. In other words, it is desirable to:

  • Use array methods only to modify arrays.
  • Use only those array methods that are pure.

Obviously, in rare cases, we might want to ditch this recommended approach but almost always there is no need to, and so shouldn't we too.

When called on an array and provided with a callback function, filter() lets in only certain elements from the array into a new, filtered array.

In our case, we want to filter the items array, letting in only those items whose index is not equal to the index argument provided to removeItem(). And that's determined by the simple check i !== index.

Now the good news is that we're done! Yes, done!

Let's test our component on the same dummy example given in the exercise's description above:

import RemovableItemList from './RemovableItemList';

function App() {
   return (
      <RemovableItemList items={[
         'Python',
         'JavaScript',
         'PHP',
         'Dart'
      ]}/>
   );
}

Live Example

Perfect! Just as we wanted.

And this completes this exercise.