Objective
Rewrite the RemovableItemList
component to reimplement the item removal logic in a different way.
Description
In the previous Throw It Away exercise, we implemented a RemovableItemList
component that rendered a list of items, each of which could be removed using a 'Remove' button.
Upon removal, the corresponding item from an internally maintained items
array was filtered out and the new array used to update the state.
Now, in this exercise, the scenario is different.
In this exercise, you have to reimplement RemovableItemList
in a way that when an item is removed, the items
array (i.e. the state value) isn't reduced in length.
At each point in time, the length of the items
array (not the items
prop provided to the component) must be the same as the length of the items
prop provided to the component.
Besides, the items
state array must be an array of objects, each having two properties:
text
— specifies the text of the item.removed
— a Boolean, specifies whether the item has been removed or not.
Using this array of objects, you have to render the list of <li>
elements.
Another thing to keep in mind is that you must NOT mutate anything inside the removeItem()
function (that we defined in the exercise) — every value should be made from scratch inside the function.
On the outside, this program obviously works in the exact same way as the program we implemented in the previous exercise. However, internally, it clearly differs in how it implements the removal logic.
As before, following is a dummy usage of this component:
function App() {
return (
<RemovableItemList items={[
'Python',
'JavaScript',
'PHP',
'Dart'
]}/>
);
}
New file
Inside the directory you created for this course on React, create a new folder called Exercise-9-Throw-It-Away-Obscurely and put the .js solution files for this exercise within it.
Solution
Here's the implementation of RemovableItemList
from the last exercise, Throw It Away:
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>
);
}
First things first, let's change the value that is used to initialize the state value items
. We need an array of objects, likewise we use map()
to create an object for each item.
This step is pretty basic and accomplished as follows:
function RemovableItemList({ items: initialItems }) {
const [items, setItems] = useState(initialItems.map(item => ({
text: item,
removed: false
})));
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>
);
}
Great!
Now the next thing to do is to modify the code rendering the list of <li>
elements. In particular, if the current item
(in line 14) has not been removed (i.e. has removed
set to false
), only then could we render it; otherwise, we render nothing for the item
.
Also, the rendered value item
needs to be replaced with item.text
(since item
is now an object, not a string, and it's invalid to render an object in React).
This is accomplished as follows:
function RemovableItemList({ items: initialItems }) {
const [items, setItems] = useState(initialItems.map(item => ({
text: item,
removed: false
})));
function removeItem(index) {
setItems(items.filter((_, i) => i !== index));
}
return (
<ul>
{items.map((item, i) => (
!item.removed && (
<li key={i}>{item.text} <button onClick={() => removeItem(i)}>Remove</button></li>
)
))}
</ul>
);
}
So far, so good.
The last thing left now is to fix the removeItem()
function.
Recall that we're not allowed to reduce the length of the items
array; we can only modify the individual elements in it. Likewise, we can't keep using the filter()
method inside removeItem()
.
But even when modifying the individual items, this has to be done in a 'pure' fashion as instructed in the exercise's description above — we are not allowed to mutate even the individual objects of items
.
Fortunately, using the spread operator (...
), this is really easy.
Here's the new implementation of removeItem()
:
...
function removeItem(index) {
setItems(items.map((item, i) => {
if (i === index) {
return { ...item, removed: true };
}
return { ...item };
}));
}
...
The setItems()
function is provided a new array by mapping over items
; in this way, the length of the array always remains the same in every single render.
The mapping function has a very simple logic: if the index of the current item
is the same as index
(the index of the item to be removed), we return a new object having the same properties as item
but with removed: true
; otherwise, the item
object is merely copied over and returned.
It's time to test this new implementation now:
function App() {
return (
<RemovableItemList items={[
'Python',
'JavaScript',
'PHP',
'Dart'
]}/>
);
}
Perfect!
With this, we complete this exercise.