Build An Amazon Product Wishlist App Part 2/2 — Frontend

Building The ReactJS Frontend To Connect To Your Backend API

Amazon Product Wishlist App

What Are We Building

We’re building the frontend portion of the Amazon Wishlist App to allow you to search for Amazon products on Amazon.com and then add those items to your frontend we client.

Amazon Wishlist App

NOTE: This is a very basic frontend with ReactJS, so I do recommend if you’re planning on making something bigger, consider using routes, a central state manager, and a few other tools to make this more than it is.

Part 1 Build An Amazon Product Wishlist App Backend API

Requirements

Like Part 1, there is the same requirements for this portion.

  • NodeJS v12+
  • Yarn

Wireframe Concept

The way I’m going to structure this is by having a page that shows an account with a list of items that a user found by searching through Amazon’s products and being able to add them to their collection of items.

The different screens from left to right are:

1 — No items

2 — Searching & Adding And Item

3 — Managing Items

Wireframe Concept

Setting Up App

We’re going to scaffold out our application with Create React App with Yarn to start.

yarn create react-app amazon-search-collection;

Cleaning UP

Next let’s clean up some files and components that I’m not going to use and create a folder structure.

Removing Files:

rm src/App.css;
rm src/App.test.js;
rm src/index.css;
rm src/logo.svg;
rm src/serviceWorker.js;
rm src/setupTests.js;

Creating Folders

mkdir src/components;
mkdir src/components/App;
mv src/App.js src/components/App/index.js;

Fixing Files

File: src/index.js

// Imports
// --------------------------------
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
// Render
// --------------------------------
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

File: src/components/App/index.js

// Imports
// --------------------------------
import React from 'react';
// Render
// --------------------------------
export default () => <div>App Starts Here</div>;
localhost:3000

Styled Components

I like to add styled-components because it means I don’t have to worry about doing any additional modifications to parse less or sass, but still get the basic functionality of variables and adding functions to my css.

yarn add styled-components;

Creating Global

mkdir src/styles;
mkdir src/styles/GlobalStyles;
touch src/styles/GlobalStyles/index.js;

File: src/styles/GlobalStyles/index.js

// Imports
// --------------------------------
import { createGlobalStyle } from 'styled-components';
// Render
// --------------------------------
export default createGlobalStyle``;

File: src/index.js

// Imports
// --------------------------------
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import GlobalStyles from './styles/GlobalStyles';
// Render
// --------------------------------
ReactDOM.render(
<React.StrictMode>
<GlobalStyles />
<App />
</React.StrictMode>,
document.getElementById('root')
);

Normalize

I realize there is a way to use Normalize as a component, but I think I’m just going to use a CDN to load Normalize into the application.

File: public/index.html

...
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
<title>Amazon Search Collection</title>
</head>
<body>

Font Support

Personally I like sans-serif fonts more than serif fonts, so we’ll use Roboto for this from Google Fonts.

Selecting Our Font Weights
Getting The Embed Code

Adding Roboto To App

File: public/index.html

...
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap" rel="stylesheet">
<title>Amazon Search Collection</title>
...

File: src/styles/GlobalStyles/index.js

...// Render
// --------------------------------
export default createGlobalStyle`
body {
font-family: 'Roboto', sans-serif;
}

`;

Creating Layout

Now we have everything I need to start creating the layout.

We’ll start by taking advantage of styled components and create 2 main sections, the header and the main section for the content. We’ll get to creating the modal later.

Creating Header

I’m going to borrow the background and profile picture graphics from my twitter to help out with the overall look and feel.

https://twitter.com/codingwithmanny

Let’s first start off by creating the styled component for the header.

touch src/components/App/styles.js;

File: src/components/App/styles.js

// Imports
// --------------------------------
import styled from 'styled-components';
// Exports
// --------------------------------
export const Header = styled.header``;

We can now import this component into our main App component.

File: src/components/App/index.js

// Imports
// --------------------------------
import React from 'react';
import { Header } from './styles';
// Render
// --------------------------------
export default () => <div>
<Header background="https://pbs.twimg.com/profile_banners/1060254597443452930/1541644308/1500x500">
<div>
<a href="https://twitter.com/codingwithmanny">
<img alt="codingwithmanny" src="https://pbs.twimg.com/profile_images/1060259296305430528/PN3RyKxE_400x400.jpg" />
<h1>@codingwithmanny</h1>
<p>Personal Wishlist</p>
</a>
</div>
<button>Add Item</button>
</Header>
</div>;

Using some css let’s make this more appealing. For this though I’m going to add a dependency called color which I have used for all my projects when I want to use a base colour for the application but I want to darken it or lighten by a bit.

yarn add color;

File: src/components/App/styles.js

// Imports
// --------------------------------
import styled from 'styled-components';
import Color from 'color';
// Exports
// --------------------------------
export const Header = styled.header`
background: ${props => props.background ? `url(${props.background}) center center` : 'lightgrey'};
background-size: cover;
width: 100%;
height: 300px;
position: relative;
> div {
backdrop-filter: blur(30px);
display: flex;
width: 100%;
height: 300px;
justify-content: center;
align-items: center;
a {
display: block;
width: 250px;
height: 130px;
text-decoration: none;
img {
display: block;
margin: 0 auto 10px auto;
width: 100px;
height: 100px;
border-radius: 50px;
transition: all 250ms ease-in-out 0s;
}
h1 {
display: block;
height: 20px;
line-height: 20px;
color: white;
font-weight: bold;
font-size: 18px;
text-align: center;
}
p {
display: block;
font-size: 12px;
text-align: center;
color: lightgray;
}
&:hover {
img {
transform: scale(1.05);
}
}
}
}
button {
position: absolute;
cursor: pointer;
bottom: -20px;
left: 0;
right: 0;
margin: 0 auto;
background: #10c694;
height: 40px;
color: white;
border: none;
font-size: 12px;
padding: 0 60px;
border-radius: 20px;
text-transform: uppercase;
transition: all 250ms ease-in-out 0s;
&:hover {
background: ${Color('#10c694').darken(0.2).toString()};
}
}
`;

With that we should get the following:

Amazon Search Collection

Creating Items

For this next part we’re still going to store all our css in our App/styles.js file but separate them a little to be able to account for a main section and then use article for individual items.

For this we’ll also use some live data that we got from our API as a base.

{
"name": "CASOFU Burritos Blanket, Giant Flour Tortilla Throw Blanket, Novelty Tortilla Blanket for Your Family, Soft and Comfortable Flannel Taco Blanket for Kids. (Burrito-a, 60inches)",
"url": "/CASOFU-Tortilla-Comfortable-Burrito-60inches/dp/B07QTHK8K9/ref=sr_1_1?dchild=1&keywords=burrito+blanket&qid=1586045729&sr=8-1",
"image": "https://m.media-amazon.com/images/I/811OenUrCyL._AC_UL320_ML3_.jpg",
"price": "19.94"
}

We’ll modify the main component to include local state and insert that as a base wrapped as an Array.

File: src/components/App/index.js

// Imports
// --------------------------------
import React, { useState } from 'react';

...
export default () => {
const [items, setItems] = useState([
{
"name": "CASOFU Burritos Blanket, Giant Flour Tortilla Throw Blanket, Novelty Tortilla Blanket for Your Family, Soft and Comfortable Flannel Taco Blanket for Kids. (Burrito-a, 60inches)",
"url": "/CASOFU-Tortilla-Comfortable-Burrito-60inches/dp/B07QTHK8K9/ref=sr_1_1?dchild=1&keywords=burrito+blanket&qid=1586045729&sr=8-1",
"image": "https://m.media-amazon.com/images/I/811OenUrCyL._AC_UL320_ML3_.jpg",
"price": "19.94"
}
]);
return (
<div>
...
</div>);
};

Next we’ll add main and section styled components in our App/styles.js file.

File: src/components/App/styles.js

// Imports
// --------------------------------
import styled from 'styled-components';
import Color from 'color';
// Exports
// --------------------------------
export const Header = styled.header`

...

export const Item = styled.article``;
export const Main = styled.main``;

And then we’ll add them to our main App component.

File: src/components/App/index.js

// Imports
// --------------------------------
import React, { useState } from 'react';
import { Header, Main, Item } from './styles';
// Render
// --------------------------------
export default () => {
...
return (
...
</Header>
<Main>
{items && items.length > 0
? <section>
{items.map((i, k) => <Item key={`Item-${k}`}>
<dl>
<dt>...</dt>
<dd>Delete</dd>
</dl>
<span>${i.price}</span>
<img alt={i.name} src={i.image} />
<h3>{i.name}</h3>
<a href={`https://www.amazon.com${i.url}`} target="_blank" rel="noopener noreferrer">View On Amazon</a>
</Item>)}
</section>
: <section><p>No items yet<br/><span>Start By Adding An Item</span></p></section>}
</Main>
</div>);
}

We’ll get the following, so now we have the structure for things, we just need to add some styling to it.

Adding Items HTML Structure

We now need to account for both the styling of the individual items and also the spacing between other items.

File: src/components/App/styles.js

...export const Item = styled.article`
background: white;
box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.15);
height: 300px;
padding: 40px 20px 20px 20px;
position: relative;
transition: all 250ms ease-in-out 0s;
dl {
padding: 0;
margin: 0;
position: absolute;
top: 20px;
dt {
background: #ddd;
opacity: 0;
cursor: pointer;
height: 31px;
width: 30px;
text-align: center;
line-height: 24px;
padding: 0 6px;
border-radius: 3px;
transition: all 250ms ease-in-out 0s;
}
dd {
display: none;
cursor: pointer;
color: #b16060;
background: #ffdede;
margin: 0;
width: 100px;
padding: 0 12px;
font-weight: bold;
font-size: 14px;
height: 40px;
line-height: 40px;
border-radius: 0px 3px 3px 3px;
transition: all 250ms ease-in-out 0s;
&:hover {
background: #b10000;
color: white;
}
}
&:hover {
dt {
border-radius: 3px 3px 0px 0px;
}
dd {
display: block;
}
}
}
&:hover {
box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.3);
transform: scale(1.01);
dl {
dt {
opacity: 1;
}
}
}
img {
display: block;
width: auto;
height: 180px;
margin: 0 auto;
top: 40px;
bottom: 0;
left: 0;
right: 0;
}
span {
position: absolute;
top: 20px;
right: 20px;
text-align: right;
border: 1px solid #ddd;
font-size: 14px;
line-height: 21px;
height: 21px;
padding: 4px 8px;
border-radius: 3px;
color: #666;
}
h3 {
font-weight: normal;
font-size: 14px;
line-height: 21px;
max-height: 42px;
position: absolute;
bottom: 60px;
left: 25px;
right: 25px;
overflow: hidden;
}
a {
cursor: pointer;
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
display: block;
margin: 0 auto;
line-height: 40px;
text-align: center;
text-decoration: none;
background: #10c694;
height: 40px;
color: white;
border: none;
font-size: 12px;
width: calc(100% - 40px);
border-radius: 20px;
text-transform: uppercase;
transition: all 250ms ease-in-out 0s;
&:hover {
background: ${Color('#10c694').darken(0.2).toString()};
}
}
`;
export const Main = styled.main`
padding: 60px 0;
display: flex;
justify-content: center;
> section {
display: flex;
flex-wrap: wrap;
margin: 0 -15px;
width: 960px;
> ${Item} {
margin: 0 15px 30px 15px;
flex: 1 1 1;
width: calc(33.33% - 30px - 40px);
}
}
`;

We should now get the following UI.

Styling Items

Before we wrap this section up, we’ll also style things when there is no items.

Comment out the local state to display the no items message.

File: src/components/App/index.js

... // Render
// --------------------------------
export default () => {
const [items, setItems] = useState([
// {
// "name": "CASOFU Burritos Blanket, Giant Flour Tortilla Throw Blanket, Novelty Tortilla Blanket for Your Family, Soft and Comfortable Flannel Taco Blanket for Kids. (Burrito-a, 60inches)",
// "url": "/CASOFU-Tortilla-Comfortable-Burrito-60inches/dp/B07QTHK8K9/ref=sr_1_1?dchild=1&keywords=burrito+blanket&qid=1586045729&sr=8-1",
// "image": "https://m.media-amazon.com/images/I/811OenUrCyL._AC_UL320_ML3_.jpg",
// "price": "19.94"
// }
]);
...

And we’ll add the following styling to it:

File: /src/components/App/styles.js

...export const Main = styled.main`
padding: 60px 0;
display: flex;
justify-content: center;
> section {
display: flex;
flex-wrap: wrap;
margin: 0 -15px;
width: 960px;
> ${Item} {
margin: 0 15px 30px 15px;
flex: 1 1 1;
width: calc(33.33% - 30px - 40px);
}
p {
display: block;
text-align: center;
margin: 0 auto;
font-size: 14px;
color: #999;
span {
display: block;
margin-top: 10px;
cursor: pointer;
text-decoration: underline;
color: #10c694;
}
}

}
`;
No Items Styling

Creating Modal

For this part, I’m going to expedite things a little by adding the styles and the JSX code needed for make the modal show.

File: src/components/App/styles.js

...export const Modal = styled.div`
display: block;
position: fixed;
background: ${Color('#ffffff').alpha(0.7).toString()};
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
overflow: scroll;
> div {
background: #ffffff;
box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.15);
display: block;
width: 640px;
height: auto;
margin: 100px auto;
position: relative;
border-radius: 6px;
h1 {
display: block;
border-bottom: 1px solid #dddddd;
font-size: 21px;
font-weight: normal;
height: 60px;
line-height: 60px;
margin: 0;
padding: 0 20px;
}
> button {
position: absolute;
right: 15px;
top: 10px;
cursor: pointer;
background: #10c694;
height: 40px;
color: white;
border: none;
font-size: 21px;
padding: 0 20px;
border-radius: 20px;
text-transform: uppercase;
transition: all 250ms ease-in-out 0s;
&:hover {
background: ${Color('#10c694').darken(0.2).toString()};
}
}
input {
display: block;
background: #efefef;
border: none;
border-radius: 4px;
height: 50px;
width: calc(100% - 32px);
padding: 0 16px;
margin: 20px auto;
}
section {
border-top: 1px solid #dddddd;
> div {
padding: 20px;
svg {
display: block;
margin: 0 auto;
}
}
article {
max-height: 132px;
padding: 10px 20px;
display: flex;
align-items: center;
border-bottom: 1px solid #dddddd;
&:last-child {
border-bottom: none;
}
img {
height: 100px;
}

h3 {
font-size: 14px;
font-weight: normal;
padding: 10px 20px;
}

span {
font-size: 12px;
text-align: right;
border: 1px solid #ddd;
line-height: 21px;
height: 21px;
padding: 4px 8px;
border-radius: 3px;
color: #666;
margin-right: 10px;
}

button {
cursor: pointer;
background: #10c694;
height: 40px;
color: white;
border: none;
font-size: 21px;
padding: 0 20px;
border-radius: 20px;
text-transform: uppercase;
transition: all 250ms ease-in-out 0s;
&:hover {
background: ${Color('#10c694').darken(0.2).toString()};
}
}
}
}
}
`;

File: src/components/App/index.js

// Imports
// --------------------------------
import React, { useState } from 'react';
import { Header, Main, Item, Modal } from './styles';
...const [showModal, setShowModal] = useState(true);return (
...
</Main>
<Modal show={showModal}>
<div>
<button>&times;</button>
<h1>Add Item</h1>
<input type="search" value="" placeholder="Search for products" />
<section>
<div>
<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 38 38" stroke="#666666">
<g fill="none">
<g transform="translate(1 1)" strokeWidth="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18" transform="rotate(277.527 18 18)">
<animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</div>
<article>
<img src={items[0].image} />
<h3>{items[0].name}</h3>
<span>${items[0].price}</span>
<button>+</button>
</article>
<article>
<img src={items[0].image} />
<h3>{items[0].name}</h3>
<span>${items[0].price}</span>
<button>+</button>
</article>
<article>
<img src={items[0].image} />
<h3>{items[0].name}</h3>
<span>${items[0].price}</span>
<button>+</button>
</article>
<article>
<img src={items[0].image} />
<h3>{items[0].name}</h3>
<span>${items[0].price}</span>
<button>+</button>
</article>
<article>
<img src={items[0].image} />
<h3>{items[0].name}</h3>
<span>${items[0].price}</span>
<button>+</button>
</article>
</section>
</div>
</Modal>
</div>);
};
Modal Styling

Adding Modal Functionality

Next we’re going to add the ability to hide and show the modal.

File: src/components/App/index.js

const [showModal, setShowModal] = useState(false);const onClickToggleModal = () => {
setShowModal(!showModal);
}
...<button onClick={onClickToggleModal}>Add Item</button>...<Modal show={showModal}>
<div>
<button onClick={onClickToggleModal}>&times;</button>

And then add the appropriate styling.

File: src/components/App/styles.js

export const Modal = styled.div`
display: block;
position: fixed;
background: ${Color('#ffffff').alpha(0.7).toString()};
top: ${props => props.show ? '0' : '100%'};
opacity: ${props => props.show ? '1' : '0'};
transition: opacity 250ms ease-in-out 0s;

bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
overflow: scroll;

Creating Search Functionality

Next, we’ll be implementing a live search, so we’ll need to add both a local state to manager the search value, and a function to handle the http request once the user has stopped typing.

First thing we need to do is create four new local states for React to manage.

One if for the search keyword, another is for the search results, another is for the timeout to know when to search when the user has stopped typing, and the last is to set when the the http request has been sent to disable the search field until the result has come back with the results (this is because the search may take a few seconds and we want to avoid sending out multiple lengthy requests for things the user didn’t request yet).

File: src/components/App/index.js

const [showModal, setShowModal] = useState(false);
const [search, setSearch] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [timeoutState, setTimeoutState] = useState(null);
const [fetching, setFetching] = useState(false);

The search input will need two event handlers tied to it, the onChange to handle the user’s typing, and onKeyUp to handle when the user has stopped typing. We’ll also link the disable functionality with our new fetching state.

File: src/components/App/index.js

const httpRequest = query => {
fetch(`http://localhost:5000/search?q=${query}`)
.then(response => {
const json = response.json();
if (response.ok) {
return json;
}
return Promise.reject('Something went wrong.');
})
.then(json => {
setSearchResults(json.data);
setFetching(false);
})
.catch(e => {
console.log('e', e);
setFetching(false);
});
};
const onChangeSearch = event => {
setSearch(event.target.value);
};
const onKeyUpSearch = event => {
if (search && search.length > 0) {
clearTimeout(timeoutState);
setTimeoutState(setTimeout(() => {
httpRequest(search)
setFetching(true);
}, 500));
}
};
...

<h1>Add Item</h1>
<input disabled={fetching} type="search" value={search} onChange={onChangeSearch} onKeyUp={onKeyUpSearch} placeholder="Search for products" />

For the results, we’ll also have to modify our Modal section array map.

File: src/components/App/index.js

<input disabled={fetching} type="search" value={search} onChange={onChangeSearch} onKeyUp={onKeyUpSearch} placeholder="Search for products" />
<section>
{fetching && <div>
<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 38 38" stroke="#666666">
<g fill="none">
<g transform="translate(1 1)" strokeWidth="2">
<circle strokeOpacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18" transform="rotate(277.527 18 18)">
<animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</div>}
{!fetching && searchResults && searchResults.length > 0 && searchResults.map((i, k) => <article key={`items-${k}`}>
<img alt={i.name} src={i.image} />
<h3>{i.name}</h3>
<span>${i.price}</span>
<button>+</button>
</article>)}
</section>

If we try things connecting to our new API, we should get the following results.

Modal Search Results

Looks like out images and results are coming through, but there are two things we need to fix.

  1. The backend seems to be omitting some pricing
  2. Some of our images are taking too much horizontal space

Fix For #2 — Images Taking Up Too Much Space

Modal Search Results Images Too Wide

To fix number 2, we’re going to wrap the image in a div and set a finitewidth and height to fix the spacing and center the image.

File: src/components/App/index.js

<div><img alt={i.name} src={i.image} /></div>
<h3>{i.name}</h3>

File: src/components/App/styles.js

export const Modal = styled.div`
...
> div {
section {
article {
> div {
display: flex;
height: 100px;
width: 100px;
justify-content: center;
align-items: center;
img {
max-height: 100px;
max-width: 100px;
}
}
h3 {
width: 340px;
}
Modal Results Spacing Fixed

Fix For #1 — Search Results With No Pricing

The fix for the first problem is probably not that easy because Amazon, for some specific products, doesn’t always show the price on the search page. This means that we would either need to modify the number of steps needed to get the product information by clicking the product and going into the details, but that would need to be done for every single product, which extends the search request quite a bit.

In short, we’re just going to leave it as is.

Amazon.com — Items With No Prices

Lastly, we also want it so that every time the modal is shown or hidden that we reset the data from the results and the search itself. This is so when we load the modal on the first time, it’s now showing the old data. So we’ll add that functionality to our new function.

File: src/components/App/index.js

const onClickToggleModal = () => {
setShowModal(!showModal);
setSearchResults([]);
setSearch('');

}

Storing Data Locally

The last part of our application is to add the ability to store the selected items to our app to save them for later.

You could use a database for this, but for simplicity sake, I’ll be using localStorage to store the data on the Client side.

We need to do three things for functionality:

1 — Adding Items To Our State & localStorage

We’ll first add a function we’ll create to the buttons.

File: src/components/App/index.js

...
<span>${i.price}</span>
<button onClick={addItem(i)}>+</button>
...

Then we’ll create the function above.

File: src/components/App/index.js

const addItem = item => {
setItems([...items, item]);
}

But wait…

Rending Error

This is because we set the button onClick to call a function that doesn’t account for the event, which executes, and tries to re-render things infinitely. It should be rewritten as the following:

File: src/components/App/index.js

const addItem = item => _event => {
setItems([...items, item]);
}
// OR
const addItem = item => () => {
setItems([...items, item]);
}

You can go ahead and search for item and then add it and when you close the modal, you should see it added to our list.

Newly Added Item

2 — Persisting Our localStorage To Local State On Refresh

The next part is to modify our local state to persist with localStorage and when we reload our page that localStorage with then update the local state with the current data.

File: src/components/App/index.js

const addItem = item => () => {
setItems([...items, item]);
localStorage.setItem('myItems', JSON.stringify([...items, item]));
}

And then we need to use useEffect so when the app loads for the first time (or the page is refreshed) that the results from localStorage are loaded.

File: src/components/App/index.js

import React, { useState, useEffect } from 'react';...export default () => {
const [items, setItems] = useState([]);
...useEffect(() => {
setItems(JSON.parse(localStorage.getItem('myItems')) || []);
}, []);
return (
<div>
...
localStorage persisted to local state

3 —Removing Items

Lastly we’re going to add a function that removes items, with the delete dropdown we have for every item.

File: src/components/App/index.js

...<dl> 
<dt>...</dt>
<dd onClick={deleteItem(k)}>Delete</dd>
</dl>
<span>${i.price}</span>
<img alt={i.name} src={i.image} />
<h3>{i.name}</h3>
...

The reason we pass k, as in the index key, is so that we know what index to remove from our array, which makes it easier to remove.

File: src/components/App/index.js

...const addItem = item => () => {
setItems([...items, item]);
localStorage.setItem('myItems', JSON.stringify([...items, item]));
}
const deleteItem = k => () => {
const newItems = [...items.slice(0, k), ...items.slice(k + 1)]
setItems(newItems);
localStorage.setItem('myItems', JSON.stringify(newItems));
}
...
Deleting An Item
All Items Deleted

Oh wait! I almost forgot to add the functionality for the “Start By Adding An Item”.

File: src/components/App/index.js

: <section><p>No items yet<br/><span onClick={onClickToggleModal}>Start By Adding An Item</span></p></section>}

Where To You Go From Here

Next steps might include packing this up and Deploying ReactJS With Docker, or deploying this to Firebase and automating tests with CircleCI.

You could also store the data with a database, and handle the results better.

There is a ton of things you could do this with, but I wanted to show what you could build with both the backend and frontend.

If you got value from this, and/or if you think this can be improved, please let me know in the comments.

Please share it on twitter 🐦 or other social media platforms. Thanks again for reading. 🙏

Please also follow me on twitter @codignwithmanny and instagram at @codingwithmanny.

Web Application / Full Stack JavaScript Developer & Aspiring DevOps

Web Application / Full Stack JavaScript Developer & Aspiring DevOps