Build An Amazon Product Wishlist App Part 2/2 — Frontend
Building The ReactJS Frontend To Connect To Your Backend API

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.

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.
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

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>;

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.


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.

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:

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.

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.

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;
}
}
}
`;

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>×</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>);
};

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}>×</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.

Looks like out images and results are coming through, but there are two things we need to fix.
- The backend seems to be omitting some pricing
- Some of our images are taking too much horizontal space
Fix For #2 — Images Taking Up Too Much Space

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;
}

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.

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…

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.

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>
...

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));
}...


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.
