In this project we will be building an ecommerce shop for the sale of JavaScript libraries. Using React Router we'll set up and navigate between the various views required. Take some time to familiarize yourself with the provided components:
App
will be the top level component for our application.Nav
will be the top navigation bar.Landing
will be the home page displayed when the application first loads.Shop
will be the main shop page, displaying the items available.Details
will be the view in which a user views a single product's information.Cart
will be the user's cart.
Several of these have child components that are used to display products in different ways.
This project uses Redux, which is a different way of managing data in things like React. We'll be teaching how to use Redux later in the course. For the time being, we've provided all the necessary Redux code. In this project, Redux is getting pieces of data from a larger pool of data, and assigning it to the this.props of a given component.
Fork
andclone
this repository.cd
into the project directory.- Run
npm i
to download the included dependencies. - Run
npm test
to start the test suite. - Run
npm start
to spin up the development server.
To begin our project, we will be installing the required dependencies and configuring the router.
- Install React Router.
- Create a new file in
src/
namedrouter.js
. - Configure a router in
src/router.js
:- Import
Switch
andRoute
fromreact-router-dom
. - Import the following components to use as routes:
src/components/Landing/Landing.js
src/components/Shop/Shop.js
src/components/Details/Details.js
src/components/Cart/Cart.js
- Use the following combinations of paths and components for your router:
- Path: "/" - Component:
Landing
- This path should be exact. - Path: "/shop" - Component:
Shop
. - Path: "/details/:name" - Component:
Details
. - Path: "/cart" - Component:
Cart
- Path: "/" - Component:
- Import
Detailed Instructions
To begin, run npm i react-router-dom --save
to install and save React Router to the package.json
. Once that installs, create a new file in src/
named router.js
. This file will be where we create and configure our router. To begin creating our router we'll need to import React
from React ince we will be using JSX to declare our routes.
import React from 'react';
The next thing we'll need to import is react-router-dom
. We'll need Route
and Switch
from react-router-dom
. Route
is the default component used for defining a new route. Switch
is a component for determining which route to display.
import React from 'react';
import { Route, Switch } from 'react-router-dom';
We'll also need to import the components that will server as an individual view/router. The components we'll want are: Cart
, Details
, Landing
, and Shop
.
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import Cart from './components/Cart/Cart';
import Details from './components/Details/Details';
import Landing from './components/Landing/Landing';
import Shop from './components/Shop/Shop';
Now that we have all our imports we can focus on creating the router. We can do this by exporting JSX. The top level element will be our Switch
component from react-router-dom
. Create an export default statement underneathe all the import
statements.
export default (
<Switch>
</Switch>
)
We can then add our views/routes inside the Switch
component by using the Route
component from react-router-dom
. For example, if I wanted to render the Landing
component it would look like:
export default (
<Switch>
<Route component={ Landing } exact path="/" />
</Switch>
)
The code snippet above means that when the path of our browser is at "/" on our website it will render the Landing
component.
What is exact
? Exact allows us to specific in React Router v4 that we only want that component to render when the path is exactly "/". If we had a development server running on port 3000, that would mean the landing component would only render at: http://localhost:3000/
.
Let's add the rest of our views/routes. Don't worry about using exact
on these routes. The remaining components to render are: Cart
, Details
, and Shop
.
export default (
<Switch>
<Route component={ Landing } exact path="/" />
<Route component={ Shop } path="/shop" />
<Route component={ Details } path="/details/:name" />
<Route component={ Cart } path="/cart" />
</Switch>
)
The code snippet above translates to:
- Say the server is hosted locally on port 3000.
- A user goes to
http://localhost:3000/
in their browser -> TheLanding
component will render. - A user goes to
http://localhost:3000/shop
in their browser -> TheShop
component will render. - A user goes to
http://localhost:3000/details/someNameGoesHere
in their browser -> TheDetails
component will render. - A user goes to
http://localhost:3000/cart
in their browser -> TheCart
component will render.
src/router.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Cart from './components/Cart/Cart';
import Details from './components/Details/Details';
import Landing from './components/Landing/Landing';
import Shop from './components/Shop/Shop';
export default (
<Switch>
<Route component={ Landing } exact path="/" />
<Route component={ Shop } path="/shop" />
<Route component={ Details } path="/details/:name" />
<Route component={ Cart } path="/cart" />
</Switch>
)
In this step, we will take the router we just configured in src/router.js
and add it to our application in src/index.js
.
- Open
src/index.js
. - Import
BrowserRouter
fromreact-router-dom
. - Wrap the
Provider
component in aBrowserRouter
component. - Open
src/components/App.js
. - Import
router
fromsrc/router.js
. - Underneath the
Nav
component render therouter
JSX.
Detailed Instructions
Now that our router is configured in src/router.js
, we need to wrap our application in a BrowserRouter
component to make use of those routes. Open up src/index.js
and import BrowserRouter
from react-router-dom
. Then inside of ReactDOM.render
wrap the Provider
component in a BrowserRouter
component.
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<Provider store={ store }>
<App />
</Provider>
</BrowserRouter>,
document.getElementById( "root" )
);
All that is left is actually rendering our router's JSX. Let's open src/components/App.js
. Import router
from src/router.js
and render it just beneath <Nav />
. Because we are rendering the router
inside of App
, that means App
will always be visible! This is useful, because we want to display the top navigation bar on every page, and now we only have to render it once.
import router from '../router';
export function App( { children } ) {
return (
<div className="app">
<Nav />
{ router }
</div>
);
}
You should now see the Landing
component by default, and have the ability to navigate to different routes via the address bar. Try manually visiting all the different routes and make sure they are working.
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import store from "./store";
import App from "./components/App";
ReactDOM.render(
<BrowserRouter>
<Provider store={ store }>
<App />
</Provider>
</BrowserRouter>,
document.getElementById( "root" )
);
src/components/App.js
import React from "react";
import "./App.css";
import Nav from "./Nav/Nav";
import router from '../router';
export function App( { children } ) {
return (
<div className="app">
<Nav />
{ router }
</div>
);
}
export default App;
In this step, we will be setting up the Landing
component to display data and link to other views.
- Open
src/components/Landing/Landing.js
. - Import
FeaturedProduct
fromsrc/components/Landing/FeaturedProduct/FeaturedProduct.js
. - Import
Link
fromreact-router-dom
. - Modify the
h1
for "Take me to the full shop!":- This element should be wrapped in a
Link
element. - The
Link
element should have a prop calledto
that equals"/shop"
. - The
Link
element should have aclassName
prop that equals"landing__full-shop-link"
.
- This element should be wrapped in a
- Before the
return
statement of theLanding
function:- Create a variable called
products
that equals a mapping overfeaturedProducts
. - The map should have one parameter called
product
. - The map should return the following
JSX
for eachproduct
:-
JSX
<FeaturedProduct addToCart={ () => addToCart( product.id ) } description={ product.description } key={ product.id } logo={ product.logo } name={ product.name } onSale={ product.onSale } price={ product.price } />
-
- Create a variable called
- Render
products
inside thediv
with theclassName
oflanding__products-wrapper
. - Open
src/components/Landing/FeaturedProduct/FeaturedProduct.js
. - Import
Link
fromreact-router-dom
. - Update all the comments with the appropriate prop.
- Modify the
h3
element with theclassName
offeatured-produce__name
:- This element should be wrapped in a
Link
element. - The
Link
element should have ato
prop that equals{ `/details/${name}` }
.
- This element should be wrapped in a
- Modify the
p
element with theclassName
offeatured-product__price-reduced
:- This element should be inside a
ternary statement
. - If
onSale
is truthy, render thep
element. - If
onSale
is not truthy, usenull
.
- This element should be inside a
Detailed Instructions
So we have routes now but no way to get to those routes from the interface. Let's fix that by updating our Landing
component. Open src/components/Landing/Landing.js
. At the bottom of the code, you can see mapStateToProps
and connect
. That is Redux's way of giving this component data. This component will take a products prop that is an array of products that are either featured or on sale.
Let's begin by importing FeaturedProduct
from src/components/Landing/FeaturedProduct/FeaturedProduct.js
and Link
from react-router-dom
in src/components/Landing/Landing.js
. The Link
component is React Router's replacement for an <a>
tag which is used to allow the library better control over routing.
import { Link } from "react-router-dom";
import FeaturedProduct from './FeaturedProduct/FeaturedProduct';
Near the bottom of the return
, wrap the h1
with the className
of landing__full-shop-link
in a Link
. Link
will take one prop called to
that should equal "/shop"
.
<Link className="landing__full-shop-link" to="/shop">
<h1 className="landing__full-shop-link">Take me to the full shop!</h1>
</Link>
Our h1
element on the Landing
component will now route to the Shop
component when clicked on.
Now let's work on getting products to actually show up. At the top of the Landing
function create a new variable called products
. This should equal the result of mapping over featuredProducts
and returning the following JSX:
<FeaturedProduct
addToCart={ () => addToCart( product.id ) }
description={ product.description }
key={ product.id }
logo={ product.logo }
name={ product.name }
onSale={ product.onSale }
price={ product.price }
/>
When combining the JSX with the map function it'll look like:
const products = featuredProducts.map( (product) => (
<FeaturedProduct
addToCart={ () => addToCart( product.id ) }
description={ product.description }
key={ product.id }
logo={ product.logo }
name={ product.name }
onSale={ product.onSale }
price={ product.price }
/>
));
This will create an array of React components for us. More specifically an array of FeaturedProduct
components. Since we used a map, each featured product will have all the information related to that product.
We are now ready to render our products onto the landing page. Locate the div
with the className
of landing__products-wrapper
. Inside that div
render our products
.
<div className="landing__products-wrapper">
{ products }
</div>
The products should now be rendering on the page. However, the data is not being populated into the FeaturedProduct
component. Let's take a look at the FeaturedProduct.js
file and make sure it's using the props as the data source. Open up src/components/Landing/FeaturedProduct/FeaturedProduct.js
and import Link
from React Router.
import { Link } from "react-router-dom";
Now let's replace the commented our sections with the appropriate props. If you notice our props have been destructured using es6. We can tell this happening based on the code snippet below:
export default function FeaturedProduct( { addToCart, description, logo, name, onSale, price } ) {
Now we can reference each prop inside this function as addToCart
, description
, logo
, name
, onSale
, and price
. After updating all the commented out sections, our return
should look like:
return (
<div className="featured-product">
<div className="featured-product__logo-name-wrapper">
<img
alt={ `${ name } logo` }
className="featured-product__logo"
src={ logo }
/>
<h3 className="featured-product__name">{ name }</h3>
</div>
<p className="featured-product__description">{ description }</p>
<div className="featured-product__buy-wrapper">
<p className="featured-product__price-reduced">Price Reduced!</p>
<button
className="featured-product__buy"
onClick={ addToCart }
>
${ price }
</button>
</div>
</div>
);
If we take a look at our live-server ( Live server not running? Run npm start
when in the root of the project. ) we can see that our landing page is now displaying the correct data for each product. However, it looks like all the prices are reduced and we can't click on the product to go to the details page. Let's fix this. Wrap the h3
tag that holds the product name in a Link
component with a to
prop that equals { `/details/${name}` }
.
return (
<div className="featured-product">
<div className="featured-product__logo-name-wrapper">
<img
alt={ `${ name } logo` }
className="featured-product__logo"
src={ logo }
/>
<Link to={ `/details/${ name }` }>
<h3 className="featured-product__name">{ name }</h3>
</Link>
</div>
<p className="featured-product__description">{ description }</p>
<div className="featured-product__buy-wrapper">
<p className="featured-product__price-reduced">Price Reduced!</p>
<button
className="featured-product__buy"
onClick={ addToCart }
>
${ price }
</button>
</div>
</div>
);
Now each product will have a link to its own details page. All that's left now is fixing the "Price Reduced!" label to not display for every product. Let's use a ternary statement
to only display that p
element when onSale
is truthy. Otherwise just use null
.
return (
<div className="featured-product">
<div className="featured-product__logo-name-wrapper">
<img
alt={ `${ name } logo` }
className="featured-product__logo"
src={ logo }
/>
<Link to={ `/details/${ name }` }>
<h3 className="featured-product__name">{ name }</h3>
</Link>
</div>
<p className="featured-product__description">{ description }</p>
<div className="featured-product__buy-wrapper">
{
onSale
?
<p className="featured-product__price-reduced">Price Reduced!</p>
:
null
}
<button
className="featured-product__buy"
onClick={ addToCart }
>
${ price }
</button>
</div>
</div>
);
src/components/Landing/Landing.js
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import "./Landing.css";
import { addToCart } from "../../ducks/product";
import FeaturedProduct from './FeaturedProduct/FeaturedProduct';
export function Landing( { addToCart, featuredProducts } ) {
const products = featuredProducts.map( (product) => (
<FeaturedProduct
addToCart={ () => addToCart( product.id ) }
description={ product.description }
key={ product.id }
logo={ product.logo }
name={ product.name }
onSale={ product.onSale }
price={ product.price }
/>
));
return (
<main className="landing">
<h1>Featured Products</h1>
<div className="landing__products-wrapper">
{ products }
</div>
<Link className="landing__full-shop-link" to="/shop">
<h1 className="landing__full-shop-link">Take me to the full shop!</h1>
</Link>
</main>
);
}
function mapStateToProps( { products } ) {
return { featuredProducts: products.filter( product => product.featured || product.onSale ) };
}
export default connect( mapStateToProps, { addToCart } )( Landing );
src/components/Landing/FeaturedProduct/FeaturedProduct.js
import React, { PropTypes } from "react";
import { Link } from "react-router-dom";
import "./FeaturedProduct.css";
export default function FeaturedProduct( { addToCart, description, logo, name, onSale, price } ) {
return (
<div className="featured-product">
<div className="featured-product__logo-name-wrapper">
<img
alt={ `${ name } logo` }
className="featured-product__logo"
src={ logo }
/>
<Link to={ `/details/${ name }` }>
<h3 className="featured-product__name">{ name }</h3>
</Link>
</div>
<p className="featured-product__description">{ description }</p>
<div className="featured-product__buy-wrapper">
{
onSale
?
<p className="featured-product__price-reduced">Price Reduced!</p>
:
null
}
<button
className="featured-product__buy"
onClick={ addToCart }
>
${ price }
</button>
</div>
</div>
);
}
FeaturedProduct.propTypes = {
addToCart: PropTypes.func.isRequired
, description: PropTypes.string.isRequired
, logo: PropTypes.string.isRequired
, name: PropTypes.string.isRequired
, onSale: PropTypes.bool
, price: PropTypes.number.isRequired
};
FeaturedProduct.defaultProps = { onSale: false };
The links should now be setup to go the details
route. However, the component is intentionally broken. We will fix this in the next step. If your view looks like the picture above, you are good to go!
In this step, we will set up the Details
component. We'll make use of route parameters to display the correct product data. I'll go into more detailed instructions on route paramters in the detail instructions section of this step.
- Open
src/components/Details/Details.js
. - Import
Link
fromreact-router-dom
. - Update the
h3
element to link to theShop
component. .find
the correctproduct
fromproducts
- Using
match
, return the single product based on the route.- Hint: Add a
console.log
that logsmatch
. Then go into the interface and click on a route from the landing page. If you open the browser's developer tools, you should seematch
get logged. Look around this object for any useful property that can indicate what product we need to display information for.
- Hint: Add a
- Once you have that property value from
match
use it in combination with afind
method to return a single object.- Hint: Use
find
onsproducts
. Return the product whosename
is equal to thename
in our route.
- Hint: Use
- Using
- Use the
product
object that gets passed in as a paramter to theDetails
function to update all the commented out sections to the correct property value.- Hint: Add a
console.log
just above theconst
in theDetails
function of the value ofproduct
. Then go into the interface and click on a route from the landing page. If you open the browser's developer tools, you should see a log that shows an object. If you don't see an object, your.find
is working incorrectly inmapStateToProps
.
- Hint: Add a
- Create an
addToCartAndRedirect
function above thereturn
of theDetails
function:- The
buy
button should be modified to use this new function instead. - This function should call the
addToCart
action creator and pass inid
as a parameter. - This function should then
route
the user back to the previous page the user was on.- Hint: React Router is providing us with a
history
object which we can reference ashistory
. This object contains values and methods that we can use to controll our router programmatically. Try to figure out the most efficient way to go back a page.
- Hint: React Router is providing us with a
- The
Detailed Instructions
Let's begin by opening src/components/Details/Details.js
. Currently this view is broken and will throw errors if we try to navigate to it. This is because Redux is giving us all the products, but as this is the Details page, we only want to display one specific product.
To fix this we'll need to get access to our route parameters. Our component is passed a match
property through props
by react-router
. This is also equal to an object. If we take a look at that object we'll see it has a property called params
that is also equal to an object. This params
object contains all the params in our URL.
When we created our route for details we specified we wanted a parameter called name
. Because of this if we take a look at match.params
(we deconstructed props
in the component declaration) we'll see a property called name
that equals a string. Since our landing page has three products this name will come in three different forms. You will see match
come in these forms:
// Backbone
ownProps = {
match: {
params: {
name: 'Backbone'
}
}
}
// React
ownProps = {
match: {
parmas: {
name: 'React'
}
}
}
// Vue
ownProps = {
match: {
params: {
name: 'Vue'
}
}
}
Knowing this object structure we can combine the value of match.params.name
with a .find
to get the exact product object we need for our component. Let's write a function that will take the name we get from match.params
and search through the products array to find the specific product we want to display.
We have access to products
from the props
deconstruction. It is an array that contains all the product
objects. Let's use a .find
on that array to return the object whose name
property equals the name property on match.params.name
.
const product = products.find( product => product.name === match.params.name )
Now that the component has access to the proper product object we can fill in the commented out sections with the correct data. Since we deconstructed the product object for you, we can look at that to determine what properties are on the product
object. The product object will have a description
, id
, logo
, name
, and price
. Let's add these values to the commented out sections.
return (
<div className="details">
<h3 className="details__back-to-shop">Back to shop</h3>
<img
alt={ name }
className="details__logo"
src={ logo }
/>
<h1 className="details__name">{ name }</h1>
<p className="details__description">{ description }</p>
<button
className="details__buy"
onClick={ addToCart( id ) }
>
Buy now for ${ price }!
</button>
</div>
);
Now when we visit the details of any of the featured products we should see it's data populate the page. Let's focus on the functionality of the component now that we have the data we need. If we take a look at our h3
element we'll notice it needs to be a link back to the Shop
component. Let's import Link
from react-router-dom
and wrap the h3
in a Link
that has a prop called to
that equals "/shop"
.
import { Link } from "react-router-dom";
<Link to="/shop">
<h3 className="details__back-to-shop">Back to shop</h3>
</Link>
A user will now be able to navigate back to the store if they are not interested in the presented product. All that's left is to make our Buy
button functional by adding the product to the cart and then routing the user back to the previous page they were on. If we take a look at our Details
function it looks like it is taking in a history
parameter. This is also something react-router
is adding to our application. This object represents the window.History
api. On this history
object that gets passed in for us, we can see that it has a function called goBack
. We can call that function after dispatching our addToCart
action.
function addToCartAndRedirect() {
addToCart( id );
history.goBack();
}
Don't forget to update the JSX to call this new function instead of the addToCart
action creator.
<button
className="details__buy"
onClick={ addToCartAndRedirect }
>
Buy now for ${ price }!
</button>
src/components/Details/Details.js
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import "./Details.css"
import { addToCart } from "../../ducks/product";
export function Details( { addToCart, history, products, match } ) {
const product = products.find( product => product.name === match.params.name )
const {
description,
id,
logo,
name,
price,
} = product;
function addToCartAndRedirect() {
addToCart( id );
history.goBack();
}
return (
<div className="details">
<Link to="/shop">
<h3 className="details__back-to-shop">Back to shop</h3>
</Link>
<img
alt={ name }
className="details__logo"
src={ logo }
/>
<h1 className="details__name">{ name }</h1>
<p className="details__description">{ description }</p>
<button
className="details__buy"
onClick={ addToCartAndRedirect }
>
Buy now for ${ price }!
</button>
</div>
);
}
function mapStateToProps( state ) {
return { products: state.products };
}
export default connect( mapStateToProps, { addToCart } )( Details );
In this step, we will set up the top navigation bar to display cart information and provide some links.
- Open
src/components/Nav/Nav.js
. - Import
Link
fromreact-router-dom
. - Wrap the
div
with aclassName
ofnav__header-wrapper
in aLink
component:- The
Link
component should have ato
prop that equals"/"
.
- The
- Wrap the
p
element with theclassName
ofnav__cart
in aLink
component:- The
Link
component should have ato
prop that equals"/cart"
. - Update the text of the
p
element to displaycartTotal
. This value comes from themapStateToProps
function below.
- The
src/components/Nav/Nav.js
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import "./Nav.css";
import javascriptLogo from "../../assets/javascript.svg";
export function Nav( { cartTotal } ) {
return (
<nav className="nav">
<Link to="/">
<div className="nav__header-wrapper">
<img
alt="javascript logo"
className="nav__javascript-logo"
src={ javascriptLogo }
/>
<h3 className="nav__header">
The JavaScript Framework Shop
</h3>
</div>
</Link>
<Link to="/cart">
<p className="nav__cart">Cart ( ${ cartTotal } )</p>
</Link>
</nav>
);
}
function mapStateToProps( { products, productsInCart } ) {
return {
cartTotal: products
.filter( product => productsInCart.includes( product.id ) )
.reduce( ( total, { price } ) => total + price, 0 )
.toFixed( 2 )
};
}
export default connect( mapStateToProps )( Nav );
In this step, we will set up the Shop
view and its child components.
- Open
src/components/Shop/Shop.js
. - Import
ProductTile
fromsrc/components/Shop/ProductTile/ProductTile.js
. - Create a variable called
productTiles
above thereturn
of theShop
function:- This variable should equal a mapping over
products
that returns the following JSX:-
JSX
<ProductTile addToCart={ () => addToCart( product.id ) } key={ product.id } logo={ product.logo } name={ product.name } price={ product.price } />
-
- This variable should equal a mapping over
- Inside the
div
with theclassName
of"shop__products-wrapper"
render theproductTiles
variable. - Open
src/components/Shop/ProductTile/ProductTile.js
. - Update the commented out sections to use
props
. - Wrap the
h3
element in aLink
component:- The
Link
component should have ato
prop that equals{ `/details/${ name }` }
.
- The
Detailed Instructions
No detailed instructions for this step! This step is very similar to how we set up the Landing
and FeaturedProducts
components. Refer back to that to help.
src/components/Shop/Shop.js
import React from "react";
import { connect } from "react-redux";
import "./Shop.css";
import { addToCart } from "../../ducks/product";
import ProductTile from './ProductTile/ProductTile';
export function Shop( { addToCart, products } ) {
const productTiles = products.map( (product) => (
<ProductTile
addToCart={ () => addToCart( product.id ) }
key={ product.id }
logo={ product.logo }
name={ product.name }
price={ product.price }
/>
));
return (
<div className="shop">
<h1 className="shop__header">Shop</h1>
<div className="shop__products-wrapper">
{ productTiles }
</div>
</div>
);
}
function mapStateToProps( { products } ) {
return { products };
}
export default connect( mapStateToProps, { addToCart } )( Shop );
src/components/Shop/ProductTile/ProductTile.js
import React, { PropTypes } from "react";
import { Link } from "react-router-dom";
import "./ProductTile.css";
export default function ProductTile( { addToCart, logo, name, price } ) {
return (
<div className="product-tile">
<section className="product-tile__info">
<Link to={ `/details/${ name }` }>
<h3> { name } </h3>
</Link>
<button
className="product-tile__buy"
onClick={ addToCart }
>
${ price }
</button>
</section>
<section className="product-tile__logo-wrapper">
<img
className="product-tile__logo"
alt={ `${ name } logo` }
src={ logo }
/>
</section>
</div>
);
}
ProductTile.propTypes = {
addToCart: PropTypes.func.isRequired,
logo: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired
};
In this step, we will allow users to checkout from the cart view. We will create a new component to thank them for their purchase.
- Create a folder in
src/components/
calledThankYou
. - Inside the
ThankYou
folder you just created, create aThankYou.js
file. - Inside the
ThankYou
folder you just created, create aThankYou.css
file.-
CSS
.thank-you { align-items: center; display: flex; flex-direction: column; justify-content: center; margin-top: 25px; }
-
- Create a basic react component called
ThankYou
insrc/components/ThankYou/ThankYou.js
:- Import
React
fromreact
. - Import
./ThankYou.css
. - Import
thanks
fromsrc/assets/thanks.gif
. - The
ThankYou
component should return the following JSX:-
JSX
<div className="thank-you"> <img role="presentation" src={ thanks } /> <h3>Thank you for your purchase!</h3> </div>
-
- Import
- Open
src/router.js
. - Import the
ThankYou
fromsrc/components/ThankYou/ThankYou.js
. - Create a new
route
where thepath
is"/thank-you"
and the component isThankYou
. - Open
src/components/Cart/Cart.js
. - Create a function called
checkoutAndRedirect
just above thereturn
statement:- This function should call the
checkout
action creator. - This function should use
history.push
to route to"/thank-you"
.
- This function should call the
- Update the
button
with theclassName
of"cart__checkout"
to call thecheckoutAndRedirect
function.
Detailed Instructions
Let's begin by creating a ThankYou
component in src/components/ThankYou/ThankYou.js
. You'll need to create the ThankYou
folder and ThankYou
javascript file. Inside the javascript file let's create a basic react component that returns the JSX from the instructions above.
import React from "react";
export default function ThankYou() {
return (
<div className="thank-you">
<img
role="presentation"
src={ thanks }
/>
<h3>Thank you for your purchase!</h3>
</div>
)
}
We're now ready to configure a route with this new ThankYou
component. Open src/router.js
and import the ThankYou
component. Then configure a route with a path of "/thank-you"
and a component of ThankYou
.
import ThankYou from './components/ThankYou/ThankYou';
export default (
<Switch>
<Route component={ Landing } exact path="/" />
<Route component={ Shop } path="/shop" />
<Route component={ Details } path="/details/:name" />
<Route component={ Cart } path="/cart" />
<Route component={ ThankYou } path="/thank-you" />
</Switch>
)
All that's left is to hook up the button in the Cart
view to call the checkout
action creator and change the view to ThankYou
. Create a new function named checkoutAndRedirect
which takes no parameters. This function will invoke the checkout
Redux action creator, then invoke history.push( "/thank-you" )
.
function checkoutAndRedirect() {
checkout();
history.push("/thank-you");
}
Then in the JSX add an onClick
attribute that calls checkoutAndRedirect
.
<button className="cart__checkout" onClick={ checkoutAndRedirect }>Checkout</button>
src/components/ThankYou/ThankYou.js
import React from "react";
import './ThankYou.css';
import thanks from '../../assets/thanks.gif';
export default function ThankYou() {
return (
<div className="thank-you">
<img
role="presentation"
src={ thanks }
/>
<h3>Thank you for your purchase!</h3>
</div>
)
}
src/router.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Cart from './components/Cart/Cart';
import Details from './components/Details/Details';
import Landing from './components/Landing/Landing';
import Shop from './components/Shop/Shop';
import ThankYou from './components/ThankYou/ThankYou';
export default (
<Switch>
<Route component={ Landing } exact path="/" />
<Route component={ Shop } path="/shop" />
<Route component={ Details } path="/details/:name" />
<Route component={ Cart } path="/cart" />
<Route component={ ThankYou } path="/thank-you" />
</Switch>
)
src/components/Cart/Cart.js
import React from "react";
import { connect } from "react-redux";
import "./Cart.css";
import { checkout } from "../../ducks/product";
import CartItem from "./CartItem/CartItem";
export function Cart( { checkout, history, productsInCart } ) {
const products = productsInCart.map( product => (
<CartItem
key={ product.id }
logo={ product.logo }
name={ product.name }
price={ product.price }
/>
) );
const cartTotal = productsInCart.reduce( ( total, { price } ) => total + price, 0 );
function checkoutAndRedirect() {
checkout();
history.push("/thank-you");
}
return (
<div className="cart">
<h1>Cart</h1>
{
products.length === 0
?
<h3>Nothing in cart! Go buy something!</h3>
:
<main>
{ products }
<div className="cart__total">
${ cartTotal }
</div>
<button className="cart__checkout" onClick={ checkoutAndRedirect }>Checkout</button>
</main>
}
</div>
);
}
function mapStateToProps( { products, productsInCart } ) {
return { productsInCart: products.filter( product => productsInCart.includes( product.id ) ) }
}
export default connect( mapStateToProps, { checkout } )( Cart );
If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2017. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.