Become a Senior React Developer! Build a massive E-commerce app with Redux, Hooks, GraphQL, Context-API, Stripe, Firebase
Created by Andrei Neagoie, Yihua Zhang
Twitter - Andrei Neagoie
Udemy Link - Complete React Developer in 2019 (w/ Redux, Hooks, GraphQL)
๐ Get all the Sections from the Udemy Course
$$(".section--title--eCwjX").map(sections => sections.textContent);
Images pasted here are captured using Chromeโs Capture Screenshot Feature
๐ Get titles for Section 1 & 2
$$(".curriculum-item-link--curriculum-item--KX9MD").map(
title => title.textContent
);
Join Zero to Mastery Discord Channel
Introduce yourself in the Discord Community
๐ Traditional HTML, CSS and JavaScript with less cross-browser support
๐ Files are requested and served from the browser every time
๐ JQuery and Backbone JS along with AJAX provided the cross-browser support and handling JS much easier
๐ In 2010
, Google introduced SPA(Single Page Application) with AngularJS using concepts of MVC - Model View Controller and containers
๐ As the size of the application grows, it becomes harder to manage the flow with many container.
๐ In 2013
, Facebook comes with React Framework to improving the drawbacks of AngularJS
๐ Since then AngularJS evolved to Angular(Now Angular 8) and React with lots of new features.
Imperative - Modify the DOM one by one based on the current app state using JavaScript
Declarative - This is where React is developed for, we just need say the state and how the page should look like. React will do everything for us which increases the performance of DOM manipulation.
Install React Dev Tools from Chrome Web Store to debug the Original React Components.
๐ Script used to get all the titles under this topic
[
...document.getElementsByClassName("curriculum-item-link--title--zI5QT")
].forEach(title => {
console.log(title.textContent);
});
๐ Install dependencies from package.json
npm install
== yarn
๐ Install a package and add to package.json
npm install package --save
== yarn add package
๐ Install a devDependency to package.json
npm install package --save-dev
== yarn add package --dev
๐ Remove a dependency from package.json
npm uninstall package --save
== yarn remove package
๐ Upgrade a package to its latest version
npm update --save
== yarn upgrade
๐ Install a package globally
npm install package -g
== yarn global add package
๐ Basic app that we are going to build
npx create-react-app monsters-rolodex
cd monsters-rolodex
npm install
npm start
http://localhost:8080
package.json
{
"name": "monsters-rolodex",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1"
},
"scripts": {
// start the development server
"start": "react-scripts start",
// build the project - production grade
"build": "react-scripts build",
// used of testing the react app
"test": "react-scripts test",
// eject the configurations that create react app did automatically
"eject": "react-scripts eject"
// only the configurations are ejected not the application
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
๐ App start from index.html
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
public folder also contains favicon.ico
and manifest.json
for PWA
index.html
is referenced by our React app at src/index.js
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(<App />, document.getElementById("root"));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
ReactDOM
renders our <App />
by replacing the element with id root
๐ Global Styles and Service Workers are imported here
src/index.css
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
src/App.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
๐ Root of our React Component
๐ App.css and logo.svg are imported here
src/App.css
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
src/App.test.js
๐ It is used for testing
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
it("renders without crashing", () => {
const div = document.createElement("div");
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
๐ Extras
.gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
node_modules
will be the home for our modules installed via npm.
package-lock.json
will be used for checking the integrity of the packages installed.
npm run eject
Gives the Webpack and other configuration that are created under-hood when creating a new react app using creat-react-app
React.Component
src/App.js
import React, { Component } from "react";import logo from "./logo.svg";
import "./App.css";
class App extends Component { constructor() { super(); this.state = { string: "Navin" }; } render() { return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>{this.state.string} is editing this App</p> <button onClick={() => this.setState({ string: "Navi" })}> Dont like Navin - Click Me
</button>
</header>
</div>
);
}
}
export default App;
{Component}
from react
App
function into a class extending `Componentstate
from Component by a contructor
function with super()
.this.state
this.setState()
when button clicked onClick()
Learned about how state rerenders the components on an event trigger.
๐ Learned about displaying dynamic contents using map()
src/App.js
import React, { Component } from "react";
// import logo from "./logo.svg";
import "./App.css";
class App extends Component {
constructor() {
super();
this.state = {
mosters: [
{ name: "Frankenstein", id: 1 },
{ name: "Dracula", id: 2 },
{ name: "Zombie", id: 3 }
]
};
}
render() {
return (
<div className="App">
{this.state.mosters.map(moster => {
return <h1 key={moster.id}>{moster.name}</h1>;
})}
</div>
);
}
}
export default App;
๐ Explained in Appendix 1: Key Developer Concepts
What is JSON? JSON Placeholder
๐ Learned about fetching JSON content from JSON Placeholder and update the state
src/App.js
import React, { Component } from "react";
// import logo from "./logo.svg";
import "./App.css";
class App extends Component {
constructor() {
super();
this.state = {
monsters: []
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
return (
<div className="App">
{this.state.monsters.map(monster => {
return <h1 key={monster.id}>{monster.name}</h1>;
})}
</div>
);
}
}
export default App;
๐ Explained in Appendix 1: Key Developer Concepts
๐ Learned about how to file structure components and styles
src/App.js
import React, { Component } from "react";
import "./App.css";
// Componentsimport { CardList } from "./components/card-list/card-list.component";class App extends Component {
constructor() {
super();
this.state = {
monsters: []
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
return (
<div className="App">
<CardList> {this.state.monsters.map(monster => { return <h1 key={monster.id}>{monster.name}</h1>; })} </CardList> </div>
);
}
}
export default App;
src/components/card-list/card-list.component.jsx
import React from "react";
import "./card-list.styles.css";
export const CardList = props => {
return <div className="card-list">{props.children}</div>;
};
src/components/card-list/card-list.styles.css
.card-list {
width: 85vw;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 20px;
}
src/App.js
import React, { Component } from "react";
import "./App.css";
// Components
import { CardList } from "./components/card-list/card-list.component";
class App extends Component {
constructor() {
super();
this.state = {
monsters: []
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
return (
<div className="App">
<CardList monsters={this.state.monsters} /> </div>
);
}
}
export default App;
src/components/card-list/card-list.component.jsx
import React from "react";
import "./card-list.styles.css";
import { Card } from "../card/card.component";
export const CardList = props => {
return ( <div className="card-list"> {props.monsters.map(monster => ( <Card key={monster.id} monster={monster} /> ))} </div> );};
src/components/card/card.component.jsx
import React from "react";
import "./card.styles.css";
export const Card = props => (
<div className="card-container">
<img
src={`https://robohash.org/${props.monster.id}?set=set2&size=180x180`}
alt="monster"
/>
<h2>{props.monster.name}</h2>
<p>{props.monster.email}</p>
</div>
);
src/components/card/card.styles.css
.card-container {
display: flex;
flex-direction: column;
background-color: #95dada;
border: 1px solid grey;
border-radius: 5px;
padding: 25px;
cursor: pointer;
transform: translateZ(0);
transition: transform 0.25s ease-out;
}
.card-container:hover {
transform: scale(1.05);
}
Learned about setState.
๐ Added Search Field to the App
src/App.js
import React, { Component } from "react";
import "./App.css";
// Components
import { CardList } from "./components/card-list/card-list.component";
class App extends Component {
constructor() {
super();
this.state = {
monsters: [], searchField: "" };
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
return (
<div className="App">
<input type="search" placeholder="Search monsters" onChange={e => this.setState({ searchField: e.target.value })} /> <CardList monsters={this.state.monsters} />
</div>
);
}
}
export default App;
Learned about React Synthetic Events and how they intercept HTML events.
src/App.js
import React, { Component } from "react";
import "./App.css";
// Components
import { CardList } from "./components/card-list/card-list.component";
class App extends Component {
constructor() {
super();
this.state = {
monsters: [],
searchField: ""
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
const { monsters, searchField } = this.state; const filteredMonsters = monsters.filter(monster => monster.name.toLowerCase().includes(searchField.toLowerCase()) ); return (
<div className="App">
<input
type="search"
placeholder="Search monsters"
onChange={e => this.setState({ searchField: e.target.value })}
/>
<CardList monsters={filteredMonsters} /> </div>
);
}
}
export default App;
๐ Explained in Appendix 1: Key Developer Concepts
src/App.js
import React, { Component } from "react";
import "./App.css";
// Components
import { CardList } from "./components/card-list/card-list.component";
import { SearchBox } from "./components/search-box/search-box.component";
class App extends Component {
constructor() {
super();
this.state = {
monsters: [],
searchField: ""
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
const { monsters, searchField } = this.state;
const filteredMonsters = monsters.filter(monster =>
monster.name.toLowerCase().includes(searchField.toLowerCase())
);
return (
<div className="App">
<SearchBox placeholder="Search monsters"
handleChange={e => this.setState({ searchField: e.target.value })} />
<CardList monsters={filteredMonsters} />
</div>
);
}
}
export default App;
src/components/search-box/search-box.component.jsx
import React from "react";
import "./search-box.styles.css";
export const SearchBox = ({ placeholder, handleChange }) => (
<input
className="search"
type="search"
placeholder={placeholder}
onChange={handleChange}
/>
);
src/components/search-box/search-box.styles.css
.search {
-webkit-appearance: none;
border: none;
outline: none;
padding: 10px;
width: 150px;
line-height: 30px;
margin-bottom: 30px;
}
๐ Learned on where to put the state because of the one way data flow
๐ Learned about how this
is bind to normal function and how JavaScript binds this to arrow function automatically to its context when the component gets created
src/App.js
import React, { Component } from "react";
import "./App.css";
// Components
import { CardList } from "./components/card-list/card-list.component";
import { SearchBox } from "./components/search-box/search-box.component";
class App extends Component {
constructor() {
super();
this.state = {
monsters: [],
searchField: ""
};
}
handleChange = e => { this.setState({ searchField: e.target.value }); }; componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
const { monsters, searchField } = this.state;
const filteredMonsters = monsters.filter(monster =>
monster.name.toLowerCase().includes(searchField.toLowerCase())
);
return (
<div className="App">
<SearchBox
placeholder="Search monsters"
handleChange={this.handleChange} />
<CardList monsters={filteredMonsters} />
</div>
);
}
}
export default App;
๐ Learned about Event Binding and how this works through a simple exercise
we learned about arrow functions and binding in React. A good rule of thumb is this: Use arrow functions on any class methods you define and arenโt part of React (i.e. render(), componentDidMount()).
If you want to learn more about this, have a read here
๐ Explained in Appendix 1: Key Developer Concepts
There are two ways to connect to a Github repository, through HTTPS and SSH. You can switch between the two options by clicking the switch https/ssh button after clicking clone. HTTPS does not require setup.
It is recommended by Github to clone using HTTPS according to their official documentation here. However, if you do end up using SSH and have never set it up before, there are a couple steps you must take first!
Firstly, SSH is like a unique fingerprint you generate for your computer in your terminal, which you then let your github account know about so it knows that requests from this computer using SSH (cloning/ pushing/ pulling) are safe to do.
In order to generate an SSH, please follow the instructions here
package.json
{
"name": "monsters-rolodex",
"version": "0.1.0",
"private": true,
"homepage": "http://navin-moorthy.github.io/monsters-rolodex", "dependencies": {
"gh-pages": "^2.1.0", "react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "npm run build", "deploy": "gh-pages -d build --git git" },
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link href="https://fonts.googleapis.com/css?family=Bigelow+Rules&display=swap" rel="stylesheet" /> <!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
src/index.css
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: linear-gradient( to left, rgba(7, 27, 82, 1) 0%, rgba(0, 128, 128, 1) 100% );}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
src/App.js
import React, { Component } from "react";
import "./App.css";
// Components
import { CardList } from "./components/card-list/card-list.component";
import { SearchBox } from "./components/search-box/search-box.component";
class App extends Component {
constructor() {
super();
this.state = {
monsters: [],
searchField: ""
};
}
handleChange = e => {
this.setState({ searchField: e.target.value });
};
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ monsters: users }));
}
render() {
const { monsters, searchField } = this.state;
const filteredMonsters = monsters.filter(monster =>
monster.name.toLowerCase().includes(searchField.toLowerCase())
);
return (
<div className="App">
<h1>Monsters Rolodex</h1> <SearchBox
placeholder="Search monsters"
handleChange={this.handleChange}
/>
<CardList monsters={filteredMonsters} />
</div>
);
}
}
export default App;
src/App.css
.App {
text-align: center;
}
h1 { font-family: "Bigelow Rules"; font-size: 72px; color: #0ccac4;}
src/components/search-box/search-box.styles.css
.search {
font-size: 16px; -webkit-appearance: none;
border: none;
outline: none;
padding: 10px;
width: 200px; line-height: 30px;
margin-bottom: 30px;
border-radius: 10px;
color: white; background-color: #4dd0e1;}
.search::placeholder { color: rgba(255, 255, 255, 0.5);}
src/components/card/card.styles.css
.card-container {
/*display: flex;*/ /*flex-direction: column;*/ background-color: #95dada;
border: 1px solid grey;
border-radius: 5px;
padding: 25px;
cursor: pointer;
transform: translateZ(0);
transition: transform 0.25s ease-out;
}
.card-container:hover {
transform: scale(1.05);
}
src/components/card-list/card-list.styles.css
.card-list {
width: 85vw;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr; grid-gap: 20px;
}
@media (min-width: 640px) { .card-list { grid-template-columns: 1fr 1fr 1fr; }}@media (min-width: 900px) { .card-list { grid-template-columns: 1fr 1fr 1fr; }}@media (min-width: 1160px) { .card-list { grid-template-columns: 1fr 1fr 1fr 1fr; }}
๐ Learned about plain React and ReactDOM in JavaScript using CDN packages
๐ Learned what React Library does in the background with JSX syntax
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>React Plain</title>
</head>
<body>
<div id="root">React Not Rendered</div>
<script
crossorigin
src="https://unpkg.com/react@16/umd/react.development.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
></script>
<script>
const Persons = props =>
React.createElement("div", {}, [
React.createElement("h2", {}, props.name),
React.createElement("p", {}, props.occupation)
]);
const App = () =>
React.createElement("div", {}, [
React.createElement(Persons, {
name: "Navin",
occupation: "Web Developer"
}),
React.createElement(Persons, {
name: "Vasanth",
occupation: "Mainframe Developer"
}),
React.createElement(Persons, {
name: "Boopalan",
occupation: "Python Developer"
})
]);
ReactDOM.render(
React.createElement(App),
document.getElementById("root")
);
</script>
</body>
</html>
๐ Learned the diff between Virtual DOM and DOM and how change in state changes only the affected DOM using Virtual DOM with Unidirectional Flow.
Used Chrome Dev Tool->More Tools->Rendering->Paint Flashing
to see the affected part of the DOM for state changes.
๐ Learned about how state changes asynchrously and how we can make it change in respective to prevState.
React LifeCycle Interactive Diagram
We want to create a new component in React that doesnโt need any local state management or access to lifecycle methods in the component. What kind of component should we make?
Functional components are the best type of component to render if you donโt need access to state or LifeCycle methods! It has benefits of being easy to test, easier to read, and easier to write!
๐ Reviewed all the code done in this section
๐ Run the below code to get all the sub headings of this section
$$(".curriculum-item-link--title--zI5QT").map(el => el.textContent);
๐ Instructions on how to clone and follow guide for the site
๐ Fork Yihua Repo and clone it to have our own repo
npx create-react-app crown-clothing
Removed unused codes
npm i -S node-sass
๐ Quick intro for the project files and modules that come preinstalled with create-react-app
๐ Also learned about the troubleshooting steps when encountered an error
๐ Quick intro on folder structure and how to easily understand the components without getting lost
๐ Note on both CSS and SCSS files will be included for use
cubic-bezier timing function - MDN
๐ Run the below code to get all the sub headings of this section
$$(".curriculum-item-link--title--zI5QT").map(el => el.textContent);
๐ Quick intro to React Router on how it works on the browser
npm install --save react-router-dom
๐ Brief intro to React Router on how it works via react-router-dom
๐ Deep dive into React Router on how it works on via react-router-dom
using an example repo
React Router GitHub repo example
import React from "react";
import { Route, Link } from "react-router-dom";
import "./App.css";
const HomePage = props => {
console.log(props);
return (
<div>
<button onClick={() => props.history.push("/topics")}>Topics </button> <h1>HOME PAGE</h1>
</div>
);
};
const TopicsList = props => {
return (
<div>
<h1>TOPIC LIST PAGE</h1>
<Link to={`${props.match.url}/13`}>TO TOPIC 13</Link> <Link to={`${props.match.url}/17`}>TO TOPIC 17</Link> <Link to={`${props.match.url}/21`}>TO TOPIC 21</Link> </div>
);
};
const TopicDetail = props => {
return (
<div>
<h1>TOPIC DETAIL PAGE: {props.match.params.topicId}</h1>
</div>
);
};
function App() {
return (
<div>
<Route exact path="/" component={HomePage} />
<Route exact path="/blog/asdqw/topics" component={TopicsList} />
<Route path="/blog/asdqw/topics/:topicId" component={TopicDetail} />
<Route exact path="/blog/topics" component={TopicsList} />
<Route path="/blog/topics/:topicId" component={TopicDetail} />
</div>
);
}
export default App;
import React from "react";
import { withRouter } from "react-router-dom";
import "./menu-item.styles.scss";
const MenuItem = ({ title, imageUrl, size, linkUrl, history, match }) => (
<div
className={`${size} menu-item`}
onClick={() => history.push(`${match.url}${linkUrl}`)}
>
<div
style={{ backgroundImage: `url(${imageUrl})` }}
className="background-image"
/>
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
export default withRouter(MenuItem);
๐ Run the below code to get all the sub headings of this section
$$(".curriculum-item-link--title--zI5QT").map(el => el.textContent);
Route: /shop
Route: /shop
Route: /
๐ Quick intro on how sign in and sign up component works and how state is managed in these components(ONLY LOCALLY)
Route: /signin
Route: /signin
Route: /signin
๐ Run the below code to get all the sub headings of this section
$$(".curriculum-item-link--title--zI5QT").map(el => el.textContent);
๐ Quick intro on how Firebase is gonna be taught in this section
npm i -S firebase
to the project๐ It is safe to enter the Firebase API in public
Route: /signin
๐ Nothing but a note for those who fork the original GitHub repo
App.js
Itโs possible you may encounter a google Authorization error that says 403:restricted_client. If you do, here are the instructions to fix it!
There should be a Learn More link in the popup, clicking that should take you to the Google APIs console that has three tabs under the header named Credentials, OAuth Consent Screen, and Domain Verification. Go to the OAuth Consent Screen tab and update the Application Name to โcrwn-clothingโ or any other name youโre comfortable with (i.e. the name of your project). Click on save at the bottom, then try logging into your verified Google account thereafter.
Route: /signin
๐ Quick intro on Firebase Firestore
Learned how to
๐ Explained in Appendix 1: Key Developer Concepts
src/App.js
import React from "react";
import { Switch, Route } from "react-router-dom";
import "./App.css";
import Header from "./components/header/header.component";
import HomePage from "./pages/homepage/homepage.component";
import SignInAndSignUpPage from "./pages/sign-in-and-sign-up/sign-in-and-sign-up.component";
import ShopPage from "./pages/shop/shop.component";
import { auth, createUserProfileDocument } from "./firebase/firebase.utils";
class App extends React.Component {
constructor() {
super();
this.state = {
currentUser: null
};
}
unSubscribeFromAuth = null;
componentDidMount() { this.unSubscribeFromAuth = auth.onAuthStateChanged(async userAuth => { if (userAuth) { const userRef = await createUserProfileDocument(userAuth); userRef.onSnapshot(snapShot => { this.setState({ currentUser: { id: snapShot.id, ...snapShot.data() } }); }); } else { this.setState({ currentUser: userAuth }); } }); }
componentWillUnmount() {
this.unSubscribeFromAuth = null;
}
render() {
return (
<div>
<Header currentUser={this.state.currentUser} />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/signin" component={SignInAndSignUpPage} />
<Route path="/shop" component={ShopPage} />
</Switch>
</div>
);
}
}
export default App;
import React from "react";
import "./sign-up.styles.scss";
import FormInput from "../form-input/form-input.components";
import CustomButton from "../custom-button/custom-button.components";
import { auth, createUserProfileDocument } from "../../firebase/firebase.utils";
class SignUp extends React.Component {
constructor() {
super();
this.state = {
displayName: "",
email: "",
password: "",
confirmPassword: ""
};
}
handleSubmit = async event => {
event.preventDefault();
const { displayName, email, password, confirmPassword } = this.state;
if (password !== confirmPassword) {
alert("Passwords do not match");
return;
}
try {
const { user } = await auth.createUserWithEmailAndPassword(
email,
password
);
await createUserProfileDocument(user, { displayName });
this.setState({
displayName: "",
email: "",
password: "",
confirmPassword: ""
});
} catch (error) {
console.log("Error in sign up", error.message);
}
};
handelChange = event => {
const { name, value } = event.target;
this.setState({ [name]: value });
};
render() {
const { displayName, email, password, confirmPassword } = this.state;
return (
<div className="sign-up">
<h2 className="title">I do not have a account</h2>
<span>Sign up with your email and password</span>
<form className="sign-up" onSubmit={this.handleSubmit}>
<FormInput
type="text"
name="displayName"
value={displayName}
onChange={this.handelChange}
label="Display Name"
required
/>
<FormInput
type="email"
name="email"
value={email}
onChange={this.handelChange}
label="Email"
required
/>
<FormInput
type="password"
name="password"
value={password}
onChange={this.handelChange}
label="Password"
required
/>
<FormInput
type="password"
name="confirmPassword"
value={confirmPassword}
onChange={this.handelChange}
label="Confirm Password"
required
/>
<CustomButton type="Submit">Sign Up</CustomButton>
</form>
</div>
);
}
}
export default SignUp;
.sign-up {
display: flex;
flex-direction: column;
width: 380px;
.title {
margin: 10px 0;
}
}
Route: /signin
import React from "react";
import "./sign-in.styles.scss";
import FormInput from "../form-input/form-input.components";
import CustomButton from "../custom-button/custom-button.components";
import { auth, signInWithGoogle } from "../../firebase/firebase.utils";
class SignIn extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
};
}
handleSubmit = async e => { e.preventDefault(); const { email, password } = this.state; try { await auth.signInWithEmailAndPassword(email, password); this.setState({ email: "", password: "" }); } catch (error) { console.log("Error signing in", error.message); } };
handleChange = e => {
const { value, name } = e.target;
this.setState({ [name]: value });
};
render() {
const { email, password } = this.state;
return (
<div className="sign-in">
<h2 className="title">I already have an account</h2>
<span>Sign in with you email and password</span>
<form onSubmit={this.handleSubmit}>
<FormInput
type="email"
name="email"
value={email}
handleChange={this.handleChange}
label="Email"
required
/>
<FormInput
type="password"
name="password"
value={password}
handleChange={this.handleChange}
label="Password"
required
/>
<div className="buttons">
<CustomButton type="submit">Sign In</CustomButton>
<CustomButton onClick={signInWithGoogle} isGoogleSignIn>
Sign in with Google
</CustomButton>
</div>
</form>
</div>
);
}
}
export default SignIn;
Whenever we call the onAuthStateChanged() or onSnapshot() methods from our auth library or referenceObject, we get back a function that lets us unsubscribe from the listener we just instantiated. Which lifecycle method should we use to call that unsubscribe method in?
๐ componentWillUnmount
๐ Calling the unsubscribe function when the component is about to unmount is the best way to make sure we donโt get any memory leaks in our application related to listeners still being open even if the component that cares about the listener is no longer on the page.
๐ Quick recap on this section coding
๐ Run the below code to get all the sub headings of this section
$$(".curriculum-item-link--title--zI5QT").map(el => el.textContent);
๐ Installed below three npm packages
Redux Logger || Redux || React Redux
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import store from "./redux/store";
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
src/redux/store.js
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import rootReducer from "./root-reducer";
const middlewares = [logger];
const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
src/redux/user/user.action.js
export const setCurrentUser = user => ({
type: "SET_CURRENT_USER",
payload: user
});
Header Component
import React from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { auth } from "../../firebase/firebase.utils";
import "./header.styles.scss";
import { ReactComponent as Logo } from "../../assets/crown.svg";
const Header = ({ currentUser }) => (
<div className="header">
<Link to="/">
<Logo className="logo" />
</Link>
<div className="options">
<Link to="/shop" className="option">
SHOP
</Link>
<Link to="/shop" className="option">
CONTACT
</Link>
{currentUser ? (
<div className="option" onClick={() => auth.signOut()}>
SIGN OUT
</div>
) : (
<Link className="option" to="/signin">
SIGN IN
</Link>
)}
</div>
</div>
);
const mapStatetoProps = state => ({ currentUser: state.user.currentUser});export default connect(mapStatetoProps)(Header);
App Component
import React from "react";
import { Switch, Route } from "react-router-dom";
import { connect } from "react-redux";
import { setCurrentUser } from "./redux/user/user.action";
import "./App.css";
import Header from "./components/header/header.component";
import HomePage from "./pages/homepage/homepage.component";
import SignInAndSignUpPage from "./pages/sign-in-and-sign-up/sign-in-and-sign-up.component";
import ShopPage from "./pages/shop/shop.component";
import { auth, createUserProfileDocument } from "./firebase/firebase.utils";
class App extends React.Component {
unSubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;
this.unSubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({ id: snapShot.id, ...snapShot.data() }); });
} else {
setCurrentUser(userAuth); }
});
}
componentWillUnmount() {
this.unSubscribeFromAuth = null;
}
render() {
return (
<div>
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/signin" component={SignInAndSignUpPage} />
<Route path="/shop" component={ShopPage} />
</Switch>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({ setCurrentUser: user => dispatch(setCurrentUser(user))});export default connect( null, mapDispatchToProps)(App);
๐ Added homepage redirect if the user is already logged in
๐ Conditionally render cart drop-down
Route: /shops/hats
๐ Learned how to add items to the cart and store them in Redux
๐ Added the quantity value to the existing Array
export const addItemToCart = (cartItems, cartItemToAdd) => {
const existingCartItem = cartItems.find(
cartItem => cartItem.id === cartItemToAdd.id
);
if (existingCartItem) {
return cartItems.map(cartItem =>
cartItem.id === cartItemToAdd.id
? { ...cartItem, quantity: cartItem.quantity + 1 }
: cartItem
);
}
return [...cartItems, { ...cartItemToAdd, quantity: 1 }];
};
๐ Explained in Appendix 1: Key Developer Concepts
๐ Explained in Appendix 1: Key Developer Concepts
Use Reselect for Memoization in the states that are not changed in Redux
import React from "react";
import { connect } from "react-redux";
import { toggleCartHidden } from "../../redux/cart/cart.actions";
import { ReactComponent as ShoppingIcon } from "../../assets/shopping-bag.svg";
import "./cart-icon.styles.scss";
const CartIcon = ({ toggleCartHidden, itemCount }) => ( <div className="cart-icon" onClick={toggleCartHidden}>
<ShoppingIcon className="shopping-icon" />
<span className="item-count">{itemCount}</span> </div>
);
const mapDispatchToProps = dispatch => ({
toggleCartHidden: () => dispatch(toggleCartHidden())
});
const mapStateToProps = ({ cart: { cartItems } }) => ({ itemCount: cartItems.reduce( (accumulatedQuantity, cartItem) => accumulatedQuantity + cartItem.quantity, 0 )});
export default connect(
mapStateToProps, mapDispatchToProps
)(CartIcon);
๐ Explained in Appendix 1: Key Developer Concepts
๐ Used Memoized selectors to improve our app performance from unwanted re-renders
npm i -S reselect
import { createSelector } from "reselect";
const selectCart = state => state.cart;
export const selectCartItems = createSelector(
[selectCart],
cart => cart.cartItems
);
export const selectCartItemsCount = createSelector(
[selectCartItems],
cartItems =>
cartItems.reduce(
(accumulatedQuantity, cartItem) =>
accumulatedQuantity + cartItem.quantity,
0
)
);
๐ Used createStructuredSelector to pass the state to multiple selectors easy peacy
import { createStructuredSelector } from "reselect";
๐ Make code and components simple and easier that others can understand
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { withRouter } from "react-router-dom";
import CustomButton from "../custom-button/custom-button.components";
import CartItem from "../cart-item/cart-item.component";
import { toggleCartHidden } from "../../redux/cart/cart.actions";import { selectCartItems } from "../../redux/cart/cart.selectors";
import "./cart-dropdown.styles.scss";
const CartDropDown = ({ cartItems, history, dispatch }) => ( <div className="cart-dropdown">
<div className="cart-items">
{cartItems.length ? (
cartItems.map(cartItem => (
<CartItem key={cartItem.id} item={cartItem} />
))
) : (
<span className="empty-message">Your card is empty</span>
)}
</div>
<CustomButton onClick={() => { history.push("/checkout"); dispatch(toggleCartHidden()); }} > GO TO CHECKOUT
</CustomButton>
</div>
);
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
});
export default withRouter(connect(mapStateToProps)(CartDropDown));
๐ Created a remove a cartItem action to remove the item on clear button in Checkout Component
๐ Brief intro on Local Storage and Session Storage
๐ Using Redux-persist we stored our cartItems in LocalStorage to persist even after the session close.
๐ Moved Directory State Into Redux
๐ Moved Shop Data State Into Redux
๐ Created a Collection Overview Component for /shop
page
๐ Changed all the naming for Category Page to Collection Page
Route: /shop/mens
๐ Collections are routed to its own collection using URL params
๐ Explained in Appendix 1: Key Developer Concepts
๐ Used data normalization to improve the performance by converting arrays to objects
In the previous lesson we learned about Objects (Hash Table data structure) being better for searching for items than Array. This is a common computing optimization when talking about data structures. If you want to learn more about why this is, this is a great resource for you to use.
import { createSelector } from "reselect";
const selectShop = state => state.shop;
export const selectCollections = createSelector(
[selectShop],
shop => shop.collections
);
export const selectCollectionsForPreview = createSelector( [selectCollections], collections => Object.keys(collections).map(key => collections[key]));
export const selectCollection = collectionUrlParam =>
createSelector(
[selectCollections],
collections => collections[collectionUrlParam]
);
๐ Quick intro on how stripe works and how they perform transactions
๐ Implemented stripe checkout button
๐ Note for those who clone the Instructors code because of the publishableKey in StripCheckoutButton
Heroku Dashboard || Heroku CLI || Heroku CRA BuildPack
๐ Install Heroku in Ubuntu
sudo snap install --classic heroku
๐ Check Heroku version
heroku --version
๐ Login to Heroku CLI with -i login in CLI itself with credentials
heroku login -i
๐ Create a new project with React Buildpack
heroku create navin-navi-crown-clothing --buildpack https://github.com/mars/create-react-app-buildpack
๐ Push the repo to heroku remote
git push heroku master
You can learn more about the buildpack we used in the previous video by following the documentation here
If you would like to not manually deploy the the app like we have seen in the previous video every time, and you want the app to redeploy anytime you update MASTER in your github repository, then you can set that up through Heroku by following these steps: https://devcenter.heroku.com/articles/github-integration
However, since we will be working on the project in the next sections, we recommend that you do not do this so that as you code along, even if your website breaks, your current version of the website is still live on Heroku until you decide to redeploy next.
๐ Quick note on how heroku and git works in both local and remote.
๐ Logger should only be shown in development
import { createStore, applyMiddleware } from "redux";
import { persistStore } from "redux-persist";
import logger from "redux-logger";
import rootReducer from "./root-reducer";
const middlewares = [];if (process.env.NODE_ENV === "development") { middlewares.push(logger);}
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
export const persistor = persistStore(store);
๐ Quick intro to styled components and what it help us to solve & improve
๐ Quick intro to styled components with a demo code
๐ Updated two components to use Styled Components
npm i -S styled-components
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import CartIcon from "../cart-icon/cart-icon.component";
import CartDropDown from "../cart-dropdown/cart-dropdown.component";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { selectCartHidden } from "../../redux/cart/cart.selectors";
import {
HeaderContainer,
LogoContainer,
OptionsContainer,
OptionLink
} from "./header.styles";
import { ReactComponent as Logo } from "../../assets/crown.svg";
const Header = ({ currentUser, hidden }) => (
<HeaderContainer>
<LogoContainer to="/">
<Logo className="logo" />
</LogoContainer>
<OptionsContainer>
<OptionLink to="/shop">SHOP</OptionLink>
<OptionLink to="/shop">CONTACT</OptionLink>
{currentUser ? (
<OptionLink as="div" onClick={() => auth.signOut()}> SIGN OUT
</OptionLink>
) : (
<OptionLink to="/signin">SIGN IN</OptionLink>
)}
<CartIcon />
</OptionsContainer>
{hidden ? null : <CartDropDown />}
</HeaderContainer>
);
const mapStatetoProps = createStructuredSelector({
currentUser: selectCurrentUser,
hidden: selectCartHidden
});
export default connect(mapStatetoProps)(Header);
๐ Updated difficult components styles using Styled Components
๐ Updated the project to use Styled Components completely
๐ Get titles for Section 16
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
๐ Quick intro on what we are going to solve in this section
Over the next couple of videos we are going to be covering some specific Firebase commands. Keep in mind that as a React Developer, you do not need to memorize these things and most of the time you can always refer to the firebase documentation for more information. We decided to include the process in the course so that you get a clear picture into what is involved in creating a full scale application.
If for some reason you get overwhelmed with Firestore, just keep going and use our provided code since this is not the โimportantโ part of the course.
๐ Quick intro to previously taught Firebase Concepts
๐ Quick intro to previously taught Firebase Concepts 2
src/firebase/firebase.util.js
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
const config = {
apiKey: "AIzaSyB5bIa1E55zDzEYnRe0zsw7kXxejifBsy0",
authDomain: "crown-clothing-db-ec57f.firebaseapp.com",
databaseURL: "https://crown-clothing-db-ec57f.firebaseio.com",
projectId: "crown-clothing-db-ec57f",
storageBucket: "",
messagingSenderId: "137189619024",
appId: "1:137189619024:web:1216d928d5eafe8b"
};
firebase.initializeApp(config);
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await userRef.set({ displayName, email, createdAt, ...additionalData });
} catch (error) {
console.log("Error creating users", error.message);
}
}
return userRef;
};
export const addCollectionAndDocuments = (collectionKey, objectsToAdd) => { const collectionRef = firestore.collection(collectionKey); console.log(collectionRef);};
export const auth = firebase.auth();
export const firestore = firebase.firestore();
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({ prompt: "select_account" });
export const signInWithGoogle = () => auth.signInWithPopup(provider);
export default firebase;
src/firebase/firebase.util.js
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
const config = {
apiKey: "AIzaSyB5bIa1E55zDzEYnRe0zsw7kXxejifBsy0",
authDomain: "crown-clothing-db-ec57f.firebaseapp.com",
databaseURL: "https://crown-clothing-db-ec57f.firebaseio.com",
projectId: "crown-clothing-db-ec57f",
storageBucket: "",
messagingSenderId: "137189619024",
appId: "1:137189619024:web:1216d928d5eafe8b"
};
firebase.initializeApp(config);
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await userRef.set({ displayName, email, createdAt, ...additionalData });
} catch (error) {
console.log("Error creating users", error.message);
}
}
return userRef;
};
export const addCollectionAndDocuments = async ( collectionKey, objectsToAdd) => { const collectionRef = firestore.collection(collectionKey); const batch = firestore.batch(); objectsToAdd.forEach(obj => { const newDocRef = collectionRef.doc(); batch.set(newDocRef, obj); }); await batch.commit();};
export const auth = firebase.auth();
export const firestore = firebase.firestore();
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({ prompt: "select_account" });
export const signInWithGoogle = () => auth.signInWithPopup(provider);
export default firebase;
src/firebase/firebase.utils.js
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
const config = {
apiKey: "AIzaSyB5bIa1E55zDzEYnRe0zsw7kXxejifBsy0",
authDomain: "crown-clothing-db-ec57f.firebaseapp.com",
databaseURL: "https://crown-clothing-db-ec57f.firebaseio.com",
projectId: "crown-clothing-db-ec57f",
storageBucket: "",
messagingSenderId: "137189619024",
appId: "1:137189619024:web:1216d928d5eafe8b"
};
firebase.initializeApp(config);
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await userRef.set({ displayName, email, createdAt, ...additionalData });
} catch (error) {
console.log("Error creating users", error.message);
}
}
return userRef;
};
export const addCollectionAndDocuments = async (
collectionKey,
objectsToAdd
) => {
const collectionRef = firestore.collection(collectionKey);
const batch = firestore.batch();
objectsToAdd.forEach(obj => {
const newDocRef = collectionRef.doc();
batch.set(newDocRef, obj);
});
await batch.commit();
};
export const convertCollectionsSnapshotToMap = collections => { const transformedCollection = collections.docs.map(doc => { const { title, items } = doc.data(); return { routeName: encodeURI(title.toLowerCase()), id: doc.id, title, items }; }); console.log(transformedCollection);};
export const auth = firebase.auth();
export const firestore = firebase.firestore();
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({ prompt: "select_account" });
export const signInWithGoogle = () => auth.signInWithPopup(provider);
export default firebase;
๐ Updated the shop data with firestore in Redux
src/components/with-spinner/with-spinner.component.jsx
import React from "react";
import { SpinnerContainer, SpinnerOverlay } from "./with-spinner.styles";
const WithSpinner = WrapperComponent => ({ isLoading, ...props }) => {
return isLoading ? (
<SpinnerOverlay>
<SpinnerContainer />
</SpinnerOverlay>
) : (
<WrapperComponent {...props} />
);
};
export default WithSpinner;
๐ Added Loading Spinner for our App
src/pages/shop/shop.component.jsx
import React from "react";
import { Route } from "react-router-dom";
import { connect } from "react-redux";
import { updateCollections } from "../../redux/shop/shop.actions";
import {
firestore,
convertCollectionsSnapshotToMap
} from "../../firebase/firebase.utils";
import CollectionPage from "../collection/collection.component";
import WithSpinner from "../../components/with-spinner/with-spinner.component";import CollectionsOverview from "../../components/collections-overview/collections-overview.components";
const CollectionPageWithSpinner = WithSpinner(CollectionPage);const CollectionsOverviewWithSpinner = WithSpinner(CollectionsOverview);
class ShopPage extends React.Component {
state = { loading: true };
unsubscripbeFromSnapshot = null;
componentDidMount() {
const { updateCollections } = this.props;
const collectionRef = firestore.collection("collections");
this.unsubscripbeFromSnapshot = collectionRef.onSnapshot(snapshot => {
const collectionsMap = convertCollectionsSnapshotToMap(snapshot);
updateCollections(collectionsMap);
this.setState({ loading: false }); });
}
componentWillUnmount() {
this.unsubscripbeFromSnapshot = null;
}
render() {
const { match } = this.props;
const { loading } = this.state; return (
<div className="shop-page">
<Route
exact
path={`${match.path}`}
render={props => ( <CollectionsOverviewWithSpinner isLoading={loading} {...props} /> )} />
<Route
exact
path={`${match.path}/:collectionId`}
render={props => ( <CollectionPageWithSpinner isLoading={loading} {...props} /> )} />
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
updateCollections: collectionsMap =>
dispatch(updateCollections(collectionsMap))
});
export default connect(
null,
mapDispatchToProps
)(ShopPage);
If you are still finding it difficult understanding how Higher Order Components can be useful, you have an optional video next which we explain in higher detail when HOCs are useful and how we can build them ourselves. Enjoy!
src/pages/shop/shop.component.jsx
import React from "react";
import { Route } from "react-router-dom";
import { connect } from "react-redux";
import { updateCollections } from "../../redux/shop/shop.actions";
import {
firestore,
convertCollectionsSnapshotToMap
} from "../../firebase/firebase.utils";
import CollectionPage from "../collection/collection.component";
import WithSpinner from "../../components/with-spinner/with-spinner.component";
import CollectionsOverview from "../../components/collections-overview/collections-overview.components";
const CollectionPageWithSpinner = WithSpinner(CollectionPage);
const CollectionsOverviewWithSpinner = WithSpinner(CollectionsOverview);
class ShopPage extends React.Component {
state = {
loading: true
};
componentDidMount() {
const { updateCollections } = this.props;
const collectionRef = firestore.collection("collections");
collectionRef.get().then(snapshot => { const collectionsMap = convertCollectionsSnapshotToMap(snapshot);
updateCollections(collectionsMap);
this.setState({ loading: false });
});
}
render() {
const { match } = this.props;
const { loading } = this.state;
return (
<div className="shop-page">
<Route
exact
path={`${match.path}`}
render={props => (
<CollectionsOverviewWithSpinner isLoading={loading} {...props} />
)}
/>
<Route
exact
path={`${match.path}/:collectionId`}
render={props => (
<CollectionPageWithSpinner isLoading={loading} {...props} />
)}
/>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
updateCollections: collectionsMap =>
dispatch(updateCollections(collectionsMap))
});
export default connect(
null,
mapDispatchToProps
)(ShopPage);
๐ Used Redux Thunk to asynchronously fetch data from firestore and save it in Redux
๐ Redux Thunk explanation by Andrei
๐ Fixed the collections fetching error in CollectionsPage Component
src/pages/collection/collection.container.jsx
import { compose } from "redux";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import CollectionPage from "./collection.component";
import WithSpinner from "../../components/with-spinner/with-spinner.component";
import { selectIsCollectionsLoaded } from "../../redux/shop/shop.selectors";
const mapStateToProps = createStructuredSelector({
isLoading: state => !selectIsCollectionsLoaded(state)
});
const CollectionPageContainer = compose(
connect(mapStateToProps),
WithSpinner
)(CollectionPage);
export default CollectionPageContainer;
๐ Get titles for Section 20
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
These next few videos are going to be tough. Keep in mind that getting redux-sagas in one go is usually impossible and it is something you practice multiple times to fully understand. We highly recommend you code along in this section and pause or rewatch the videos whenever you feel unsure of something. Another option is to watch this section all the way through, then come back the 2nd time around and code along once you have a general idea of the concepts.
Good luck!
src/redux/shop/shop.sagas.js
import { takeEvery } from "redux-saga/effects";
import { ShopActionTypes } from "./shop.types";
export function* fetchCollectionAsync() {
yield console.log("I am fired");
}
export function* fetchCollectionsStart() {
console.log("1");
yield takeEvery(
ShopActionTypes.FETCH_COLLECTIONS_START,
fetchCollectionAsync
);
}
src/redux/shop/shop.sagas.js
import { takeEvery, call, put } from "redux-saga/effects";
import { ShopActionTypes } from "./shop.types";
import {
fetchCollectionsSuccess,
fetchCollectionsFailure
} from "./shop.actions";
import {
firestore,
convertCollectionsSnapshotToMap
} from "../../firebase/firebase.utils";
export function* fetchCollectionAsync() { yield console.log("I am fired"); try { const collectionRef = firestore.collection("collections"); const snapshot = yield collectionRef.get(); const collectionsMap = yield call( convertCollectionsSnapshotToMap, snapshot ); yield put(fetchCollectionsSuccess(collectionsMap)); } catch (error) { yield put(fetchCollectionsFailure(error.message)); }}
export function* fetchCollectionsStart() {
console.log("1");
yield takeEvery(
ShopActionTypes.FETCH_COLLECTIONS_START,
fetchCollectionAsync
);
}
๐ Deep explanation on take(), takeEvery(), takeLatest()
src/redux/root-saga.js
import { all, call } from "redux-saga/effects";
import { fetchCollectionsStart } from "./shop/shop.sagas";
export default function* rootSaga() {
yield all([call(fetchCollectionsStart)]);
}
๐ Plan to shift our Users Auth calls into Redux Saga
๐ Implemented Google Sign In Into Sagas
๐ Implemented Email Sign In Into Sagas
๐ Implemented User persistence recreation
๐ Implemented User persistence recreation
๐ Implemented Clear Cart Saga on Sign out
๐ Implemented Sign Up Saga
๐ Get titles for Section 21
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
๐ Intro to useState hook
If you want to learn more about why the React team decided to add Hooks to the library, you can find the motivation behind their decision right from their mouth https://reactjs.org/docs/hooks-intro.html#motivation
useEffect || JSON Placeholder || useEffect Example
๐ Hooks cannot be conditionally renders in top level. Conditions should be inside the Hooks
๐ Converted SignIn Component and SignUp Component to use the State hook
import React, { useState } from "react";
import { connect } from "react-redux";
import {
googleSignInStart,
emailSignInStart
} from "../../redux/user/user.actions";
import FormInput from "../form-input/form-input.components";
import CustomButton from "../custom-button/custom-button.component";
import {
SignInContainer,
SignInTitle,
ButtonsBarContainer
} from "./sign-in.styles";
const SignIn = ({ emailSignInStart, googleSignInStart }) => {
const [UserCredentials, setCredentials] = useState({
email: "",
password: ""
});
const { email, password } = UserCredentials;
const handleSubmit = async e => {
e.preventDefault();
emailSignInStart(email, password);
};
const handleChange = e => {
const { value, name } = e.target;
setCredentials({ ...UserCredentials, [name]: value });
};
return (
<SignInContainer>
<SignInTitle>I already have an account</SignInTitle>
<span>Sign in with you email and password</span>
<form onSubmit={handleSubmit}>
<FormInput
type="email"
name="email"
value={email}
handleChange={handleChange}
label="Email"
required
/>
<FormInput
type="password"
name="password"
value={password}
handleChange={handleChange}
label="Password"
required
/>
<ButtonsBarContainer>
<CustomButton type="submit">Sign In</CustomButton>
<CustomButton
type="button"
onClick={googleSignInStart}
isGoogleSignIn
>
Sign in with Google
</CustomButton>
</ButtonsBarContainer>
</form>
</SignInContainer>
);
};
const mapDispatchToProps = dispatch => ({
googleSignInStart: () => dispatch(googleSignInStart()),
emailSignInStart: (email, password) =>
dispatch(emailSignInStart({ email, password }))
});
export default connect(
null,
mapDispatchToProps
)(SignIn);
๐ Converted some Component to use useEffect hook
๐ useEffect hook clean up function acts as a ComponentWillUnmount
A quick recap of what we have learned about useEffect:
๐ ComponentDidMount
//Class
componentDidMount() {
console.log('I just mounted!');
}
//Hooks
useEffect(() => {
console.log('I just mounted!');
}, [])
๐ ComponentWillUnmount
//Class
componentWillUnmount() {
console.log('I am unmounting');
}
//Hooks
useEffect(() => {
return () => console.log('I am unmounting');
}, [])
๐ ComponentWillReceiveProps
//Class
componentWillReceiveProps(nextProps) {
if (nextProps.count !== this.props.count) {
console.log('count changed', nextProps.count);
}
}
//Hooks
useEffect(() => {
console.log('count changed', props.count);
}, [props.count])
๐ Learned more about useEffect()
useReducer || useReducer example
There are a few other Hooks we still need to talk about such as useContext or useMemo or useCallback However, we are covering topics like these in later sections in the course when we learn a little bit more about things like ContextAPI and Performance.
So hang on tight and you will learn about them shortly as we will continue to use hooks throughout the upcoming sections!
๐ Get titles for Section 22
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
In order to have a fully functioning e commerce project with payments, we need to create a backend server for our Stripe payments. This section coming up does not talk about React, but instead, allows you to have a fully functioning application because our goal here is to have a project as complete as possible for you. However, this is not an important part of the course, so if you do not want to learn about the backend, you can skip this section, or you can just grab the code that we will provide in the last lecture of this section. The only thing you will be missing out on is the full ability to accept payments with Stripe (since currently the payment info the user sends on the frontend isnโt doing anything).
Remember, this section is completely optional!
If you do choose to skip this section and just fork and clone this repo, or any repo from this point on in the course, remember to add a file called .env
to the root folder! In that .env
file remember to add a STRIPE_SECRET_KEY
value equal to your own secret key from your stripe dashboard. You can find it in the same place where you found your publishable key in the developers tab under api keys. You will have to enter the password in to reveal it!
You will also need to connect your existing Heroku app to this new forked and cloned repo, or you have to create a new Heroku app and push to it. A quick refresher on how to do either of these:
๐ Set to an existing Heroku app
To set to an existing Heroku app you already have deployed, you need to know the name of the app you want to deploy to. To see a list of all the apps you currently have on Heroku:
heroku apps
Copy the name of the app you want to connect the project to, then run:
heroku git:remote -a <PASTE_YOUR_APP_NAME_HERE>
And now youโll have your repo connected to the heroku app under the git remote name heroku
.
If the Heroku app you connected was deploying just a create-react-app project from earlier in the lesson, you will need to remove the mars/create-react-app-buildpack
buildpack first. You can check if you have this buildpack by running:
heroku buildpacks
Which will list any buildpacks you currently have, if you see mars/create-react-app-buildpack
in the list, you can remove it by running:
heroku buildpacks:remove mars/create-react-app-buildpack
Then skip to the bottom of this article to see what to do next!
๐ To create a new Heroku app
Create a new Heroku project by typing in your terminal:
heroku create
This will create a new Heroku project for you. Then run:
git remote -v
You should see heroku https://git.heroku.com/<RANDOMLY_GENERATED_NAME_OF_YOUR_APP>
in the list. This means you have successfully connected your project to the newly created Heroku app under the git remote of heroku
.
๐ Deploying to Heroku
Before we deploy, you also need to set a config variable of STRIPE_SECRET_KEY
to the same secret key value from your stripe dashboard, the same one in your .env
file. The .env
file is only for local development, in order for our heroku production app to have access to this secret key, we add it to our Heroku projects config variables by typing:
heroku config:set STRIPE_SECRET_KEY=<YOUR_STRIPE_SECRET_KEY>
After that, you can deploy to heroku by running:
git push heroku master
You will see this warning message if you are pushing to an existing app:
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://git.heroku.com/<YOUR_HEROKU_APP_NAME>'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
This is because we are pushing to an existing app that was deploying an entirely different repository from what we have now. Simply run:
git push heroku master --force
This will overwrite the existing Heroku app with our new code.
๐ Open our Heroku project
After heroku finishes building our project, we can simply run:
heroku open
This will open up our browser and take us to our newly deployed Heroku project!
๐ Backend initialized
express || dotenv || cors || body-parser
server.js
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const path = require("path");
if (process.env.NODE_ENV !== "production") require("dotenv").config();
const app = express();
const port = process.env.PORT || 8081;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "client/build")));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client/build", "index.html"));
});
}
app.listen(port, error => {
if (error) throw error;
console.log(`Server running on port ${port}`);
});
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const path = require("path");
if (process.env.NODE_ENV !== "production") require("dotenv").config();
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const app = express();
const port = process.env.PORT || 8081;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "client/build")));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client/build", "index.html"));
});
}
app.post("/payment", (req, res) => { console.log(req.body); const { token: { id }, amount } = req.body; console.log(id); console.log(amount); const body = { source: req.body.token.id, amount: req.body.amount, curreny: "usd" }; stripe.charges.create(body, (stripeErr, stripeRes) => { if (stripeErr) { res.status(500).send({ error: stripeErr }); } else { res.status(200).send({ success: stripeRes }); } });});
app.listen(port, error => {
if (error) throw error;
console.log(`Server running on port ${port}`);
});
client/src/components/stripe-button/stripe-button.component.jsx
import React from "react";
import StripeCheckout from "react-stripe-checkout";
import axios from "axios";
import crown from "../../assets/crown.svg";
const StripeCheckoutButton = ({ price }) => {
const priceForStripe = price * 100;
const publishableKey = "pk_test_2hJtHnCWfCA14ioo1FKhoZMS00tev3ElY9";
const onToken = token => { axios({ url: "payment", method: "post", data: { amount: priceForStripe, token } }) .then(response => { alert("Payment Successful"); }) .catch(error => { console.log(`Payment error: ${error}`); alert( "There was an issue with your payment. Please make sure use the provided credit card" ); }); };
return (
<StripeCheckout
label="Pay Now"
name="Crown Clothing Ltd."
billingAddress
shippingAddress
image={crown}
description={`Your total is $${price}`}
amount={priceForStripe}
panelLabel="Pay Now"
token={onToken}
stripeKey={publishableKey}
/>
);
};
export default StripeCheckoutButton;
Removed the old React build-pack to make the node app deploy successful.
๐ Get titles for Section 23
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
๐ Quick note about cloning this repo
Context API Example Start Repo
This section will be taught from the an older commit before the introduction of advanced Redux concepts
src/contexts/collections/collections.context.js
import { createContext } from 'react';
import SHOP_DATA from './shop.data';
const CollectionsContext = createContext(SHOP_DATA);
export default CollectionsContext;
src/pages/collection/collection.component.jsx
import React, { useContext } from 'react';
import CollectionItem from '../../components/collection-item/collection-item.component';
import CollectionsContext from '../../contexts/collections/collections.context';
import './collection.styles.scss';
const CollectionPage = ({ match }) => {
const collections = useContext(CollectionsContext); const collection = collections[match.params.collectionId];
const { title, items } = collection;
return (
<div className='collection-page'>
<h2 className='title'>{title}</h2>
<div className='items'>
{items.map(item => (
<CollectionItem key={item.id} item={item} />
))}
</div>
</div>
);
};
export default CollectionPage;
src/contexts/current-user/current-user.context.js
import { createContext } from 'react';
const CurrentUserContext = createContext(undefined);
export default CurrentUserContext;
src/App.js
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import './App.css';
import HomePage from './pages/homepage/homepage.component';
import ShopPage from './pages/shop/shop.component';
import SignInAndSignUpPage from './pages/sign-in-and-sign-up/sign-in-and-sign-up.component';
import CheckoutPage from './pages/checkout/checkout.component';
import Header from './components/header/header.component';
import { auth, createUserProfileDocument } from './firebase/firebase.utils';
import CurrentUserContext from './contexts/current-user/current-user.context';
class App extends React.Component {
constructor() {
super();
this.state = {
currentUser: null
};
}
unsubscribeFromAuth = null;
componentDidMount() {
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
this.setState({
currentUser: {
id: snapShot.id,
...snapShot.data()
}
});
});
}
this.setState({ currentUser: userAuth });
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div>
<CurrentUserContext.Provider value={this.state.currentUser}> <Header /> </CurrentUserContext.Provider> <Switch>
<Route exact path='/' component={HomePage} />
<Route path='/shop' component={ShopPage} />
<Route exact path='/checkout' component={CheckoutPage} />
<Route
exact
path='/signin'
render={() =>
this.state.currentUser ? (
<Redirect to='/' />
) : (
<SignInAndSignUpPage />
)
}
/>
</Switch>
</div>
);
}
}
export default App;
src/components/header/header.component.jsx
import React, { useContext, useState } from 'react';import { Link } from 'react-router-dom';
import { auth } from '../../firebase/firebase.utils';
import CartIcon from '../cart-icon/cart-icon.component';
import CartDropdown from '../cart-dropdown/cart-dropdown.component';
import CurrentUserContext from '../../contexts/current-user/current-user.context';
import { CartContext } from '../../providers/cart/cart.provider';
import { ReactComponent as Logo } from '../../assets/crown.svg';
import './header.styles.scss';
const Header = () => {
const currentUser = useContext(CurrentUserContext); const { hidden } = useContext(CartContext);
return (
<div className='header'>
<Link className='logo-container' to='/'>
<Logo className='logo' />
</Link>
<div className='options'>
<Link className='option' to='/shop'>
SHOP
</Link>
<Link className='option' to='/shop'>
CONTACT
</Link>
{currentUser ? (
<div className='option' onClick={() => auth.signOut()}>
SIGN OUT
</div>
) : (
<Link className='option' to='/signin'>
SIGN IN
</Link>
)}
<CartIcon />
</div>
{hidden ? null : <CartDropdown />}
</div>
);
};
export default Header;
๐ Created a Cart Context to leverage the hidden value for the cart dropdown
Context API Provider Example Complete
src/providers/cart/cart.provider.jsx
import React, { createContext, useState, useEffect } from 'react';
import {
addItemToCart,
removeItemFromCart,
filterItemFromCart,
getCartItemsCount,
getCartTotal
} from './cart.utils';
export const CartContext = createContext({
hidden: true,
toggleHidden: () => {},
cartItems: [],
addItem: () => {},
removeItem: () => {},
clearItemFromCart: () => {},
cartItemsCount: 0,
cartTotal: 0
});
const CartProvider = ({ children }) => {
const [hidden, setHidden] = useState(true);
const [cartItems, setCartItems] = useState([]);
const [cartItemsCount, setCartItemsCount] = useState(0);
const [cartTotal, setCartTotal] = useState(0);
const addItem = item => setCartItems(addItemToCart(cartItems, item));
const removeItem = item => setCartItems(removeItemFromCart(cartItems, item));
const toggleHidden = () => setHidden(!hidden);
const clearItemFromCart = item =>
setCartItems(filterItemFromCart(cartItems, item));
useEffect(() => {
setCartItemsCount(getCartItemsCount(cartItems));
setCartTotal(getCartTotal(cartItems));
}, [cartItems]);
return (
<CartContext.Provider
value={{
hidden,
toggleHidden,
cartItems,
addItem,
removeItem,
clearItemFromCart,
cartItemsCount,
cartTotal
}}
>
{children}
</CartContext.Provider>
);
};
export default CartProvider;
๐ Brief explanation on when to use Redux and when to use Context API
๐ Redux-Big Projects & Context API-Small and Medium level Projects
๐ Get titles for Section 24
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
Crown Clothing GraphQL playground || GraphQL Basic Types || Prisma Server for playground
When we talk about GraphQL, it usually comes with two components: The frontend part and the backend part. As React developers, we will usually only concern ourselves with the frontend implementation of GraphQL and this is what we will be exploring over the coming videos. However, for those curious on how to best build a GraphQL server, we have provided for you the backend code that we use for this course, as well as the list of some popular options out there for building such a server:
Prisma what we use in the above link Hasura Apollo Server
Quick way to build a GraphQL server: graphql-yoga
A quick step by step guide on how to set up your own GraphQL server
Apollo Client || apollo-boost || react-apollo || graphql-npm
Created own graphql server endpoint to tackle cors errors.
src/components/collections-overview/collections-overview.container.jsx
import React from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";
import CollectionsOverview from "./collections-overview.component";
import Spinner from "../spinner/spinner.component";
const GET_COLLECTIONS = gql`
{
collections {
id
title
items {
id
name
price
imageUrl
}
}
}
`;
const CollectionsOverviewContainer = () => (
<Query query={GET_COLLECTIONS}>
{({ loading, error, data }) => {
if (loading) return <Spinner />;
return <CollectionsOverview collections={data.collections} />;
}}
</Query>
);
export default CollectionsOverviewContainer;
src/components/collections-overview/collections-overview.container.jsx
import React from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";
import CollectionPage from "./collection.component";
import Spinner from "../../components/spinner/spinner.component";
const GET_COLLECTION_BY_TITLE = gql`
query getCollectionsByTitle($title: String!) {
getCollectionsByTitle(title: $title) {
id
title
items {
id
name
price
imageUrl
}
}
}
`;
const CollectionPageContainer = ({ match }) => (
<Query
query={GET_COLLECTION_BY_TITLE}
variables={{ title: match.params.collectionId }}
>
{({ loading, data: { getCollectionsByTitle } }) => {
if (loading) return <Spinner />;
return <CollectionPage collection={getCollectionsByTitle} />;
}}
</Query>
);
export default CollectionPageContainer;
Apollo Cache || Apollo Local Resolver
src/graphql/resolvers.js
import { gql } from "apollo-boost";
export const typeDefs = gql`
extend type Mutation {
ToggleCartHidden: Boolean!
}
`;
const GET_CART_HIDDEN = gql`
{
cartHidden @client
}
`;
export const resolvers = {
Mutation: {
toggleCartHidden: (_root, _args, { cache }) => {
const { cartHidden } = cache.readQuery({
query: GET_CART_HIDDEN
});
cache.writeQuery({
query: GET_CART_HIDDEN,
data: { cartHidden: !cartHidden }
});
return !cartHidden;
}
}
};
To learn more about mutations, we recommend checking out the Apollo documentation here. Mutations are hard to grasp at first, but as with anything, once you get used to the syntax, it becomes nice and easy!
๐ Added cartItems using Apollo
Hello everyone! In the next lesson, we are going to use a method called compose
that we import from 'react-apollo'
. Unfortunately with the recent React-Apollo update to v3.0.0 itโs been removed from React-Apollo and is no longer something we can import from this library. Luckily compose was just a copy of lodashโs flowRight
. Lodash is just a small library that gives us access to a bunch of helper functions, of which flowRight
is one of! In the following lesson, anyplace you see compose
just use lodash flowRight
!
You can install lodash in your project by adding it as a dependency as follows:
If youโre using yarn:
yarn add lodash
If youโre using npm:
npm install lodash
You can then import flowRight into your file like so:
import { flowRight } from 'lodash';
and just replace any place in the lesson where we use compose
with flowRight
:
export default compose(
//...code
)(CollectionItemContainer);
becomes
export default flowRight(
// ...code
)(CollectionItemContainer);
You can find out more about this breaking change here as well: https://github.com/apollographql/react-apollo/issues/3330. So push forward with the lesson :)
๐ Added itemCount back using Apollo
As an exercise, attempt to convert the remaining instances of redux in the application over to using our Apollo local cache!
You can find a link to all the code weโve done up to now here:
https://github.com/ZhangMYihua/graphql-practice
You can also find a full solution repo at this GitHub link to check your solutions:
https://github.com/ZhangMYihua/graphql-practice-complete
๐ Tradeoff in using GraphQl over Redux
src/global.styles.js
import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
}
body {
font-family: "Open Sans Condensed", sans-serif;
padding: 20px 60px;
@media screen and (max-width: 800px){
padding: 10px;
}
}
a {
text-decoration: none;
color: black;
}
`;
๐ Responsive design for Mobiles
As a bonus exercise for those that want to try, you can continue to play around with our shopping application and customize it to your liking on mobile! Beyond that, from this point forward, you can customize and add any additional features you want in this app. As with any programming project, the opportunities to improve upon it are endless.
The rest of the sections will close out some of the lessons we learned, as well as introduce you to some bonus topics like Testing and PWAs (we will even convert our master project to a PWA so all of these mobile changes we made will now make our app behave like a mobile app), but are only optional for you to take. Enjoy!
Resources for this lecture Github: Code up to now Github: Solution
๐ Get titles for Section 22
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
App.js
import React, { useEffect, lazy, Suspense } from "react";import { Switch, Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import Header from "./components/header/header.component";
import Spinner from "./components/spinner/spinner.component";
import { selectCurrentUser } from "./redux/user/user.selectors";
import { checkUserSession } from "./redux/user/user.actions";
import { GlobalStyle } from "./global.styles";
const HomePage = lazy(() => import("./pages/homepage/homepage.component"));const ShopPage = lazy(() => import("./pages/shop/shop.component"));const SignInAndSignUpPage = lazy(() => import("./pages/sign-in-and-sign-up/sign-in-and-sign-up.component"));const CheckoutPage = lazy(() => import("./pages/checkout/checkout.component"));
const App = ({ checkUserSession, currentUser }) => {
useEffect(() => {
checkUserSession();
}, [checkUserSession]);
return (
<div>
<GlobalStyle />
<Header />
<Switch>
<Suspense fallback={<Spinner />}> <Route exact path="/" component={HomePage} />
<Route
exact
path="/signin"
render={() =>
currentUser ? <Redirect to="/" /> : <SignInAndSignUpPage />
}
/>
<Route path="/shop" component={ShopPage} />
<Route exact path="/checkout" component={CheckoutPage} />
</Suspense> </Switch>
</div>
);
};
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
const mapDispatchToProps = dispatch => ({
checkUserSession: () => dispatch(checkUserSession())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Error Boundaries || 404 Illustrations
src/components/error-boundary/error-boundary.component.jsx
import React from "react";
import {
ErrorImageOverlay,
ErrorImageContainer,
ErrorImageText
} from "./error-boundary.styles";
class ErrorBoundary extends React.Component {
constructor() {
super();
this.state = {
hasErrored: false
};
}
static getDerivedStateFromError(error) {
// process the error here
return { hasErrored: true };
}
componentDidCatch(error, info) {
console.log(error);
}
render() {
if (this.state.hasErrored) {
return (
<ErrorImageOverlay>
<ErrorImageContainer imageUrl="https://i.imgur.com/A040Lxr.png" />
<ErrorImageText>Sorry This Page is Lost in Space</ErrorImageText>
</ErrorImageOverlay>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
React.memo || React.PureComponent || React Dev Tools
๐ Learned how to use the Profiler from React Dev tools
import React, { useState, useCallback, useMemo } from 'react';
import logo from './logo.svg';
import './App.css';
const App = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const incrementCount1 = useCallback(() => setCount1(count1 + 1), [count1]);
const incrementCount2 = useCallback(() => setCount2(count2 + 1), [count2]);
const doSomethingComplicated = useMemo(() => {
console.log('I am computing something complex');
return ((count1 * 1000) % 12.4) * 51000 - 4000;
}, [count1]);
return (
<div className='App'>
<header className='App-header'>
<img src={logo} className='App-logo' alt='logo' />
Count1: {count1}
<button onClick={incrementCount1}>Increase Count1</button>
Count2: {count2}
<button onClick={incrementCount2}>Increase Count2</button>
complexValue: {doSomethingComplicated}
</header>
</div>
);
};
export default App;
๐ Build Files Size
File sizes after gzip:
remote: 225.01 KB build/static/js/2.16332b27.chunk.js
remote: 7.71 KB build/static/js/main.1424a885.chunk.js
remote: 7.21 KB build/static/js/3.941ee3f8.chunk.js
remote: 2.15 KB build/static/js/9.54c4bca4.chunk.js
remote: 1.57 KB build/static/js/6.2b158ef5.chunk.js
remote: 1.48 KB build/static/js/4.ff8f2bb2.chunk.js
remote: 1.36 KB build/static/js/5.7b8abb52.chunk.js
remote: 1.23 KB build/static/js/runtime~main.09f6eda3.js
remote: 1.09 KB build/static/js/7.8eac9c54.chunk.js
remote: 451 B build/static/js/8.79499474.chunk.js
remote: 277 B build/static/css/main.795991f2.chunk.css
Since I get this question a lot, Iโve added an info graph to help you decide what skills you should focus on and what you need to learn to succeed as a programmer.
If you are looking to create a LinkedIn profile or you already have one, you can join our LinkedIn group here. This group is meant for you to increase your LinkedIn connections, meet other coders, and also endorse each otherโs skills.
You can join the group here and then go ahead and endorse some of the memberโs skills (other people will do the same for you as they join).
If you have any questions, reach out in our private Discord chat community in the #job-hunting channel!
I have created the #alumni channel on Discord as well as the Alumni role so you can network with other graduates. Please let myself or the management team know that you have finished the course so you can get the alumni badge in the community! Simply post your completion certificate in the #alumni channel and tag the @Management Team. If you have finished the course I highly recommend you join the channel and stay up to date and network throughout your career. You never know how it may come in handy in the future.
It would be great to have the alumni follow up on their career journey such as: Did they find a new job? or Did they enroll in some further study? or even Did they launch their own business/product?
Many students would benefit from this and I hope you give back a bit to the community :)
This upcoming section is all about PWAs and part of it is from Andreiโs course The Complete Junior to Senior Web Developer Roadmap. HOWEVER, Yihua will come in at the end and actually demonstrate how to implement our app from this course as a Progressive Web App. If you already know about PWAs, then you can go straight to the Converting Our App To PWA lesson!
Enjoy :)
๐ Short intro on PWA
In case you want further reading on Progressive Web Apps:
Submitting PWA to 3 app stores
Finally, here is the link to the robofriends-redux github repository which I will be referencing in the next couple of videos which is very similar to our Monsters Rolodex App we built in this course.
Ps You can explore some of the top PWAs from around the world: https://appsco.pe/
๐ Short intro on PWA HTTPS
First, in case you have never put a website online using Github Pages, you can use this tutorial to have your Github website up and running in 5 minutes: GitHub Pages Please note that this is only for simple websites. If you want to have github pages work with create react app, we cover this in the upcoming video: Deploying Our React App
Progressive Web Apps Checklist: PWA Checklist
Finally, if you would like to implement HTTPS yourself, I recommend you use Letโs Encrypt
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
Service Worker is implemented very easily into a react based project. You can see this Git Diff to see what you would need to do to add service worker into an existing create react app project without the default service worker.
For further information on the topics covered in the previous video:
More information on push notifications for those who want to see how it is implemented
๐ Final thoughts on when and where to use the PWA
Express-sslify || Lighthouse Chrome Extension || crwn-clothing EndGame
๐ Get titles for Section 29
$$(".curriculum-item-link--title--zI5QT").map(
title => title.textContent
);
This upcoming section is all about testing and it is from Andreiโs course The Complete Junior to Senior Web Developer Roadmap. Although this isnโt a required part of this course, testing is something that will be important in your career so I have decided to include it on here. If you are new to testing, then you can watch all of the videos. If you know about testing and only want to focus on React specific testing, you can start from halfway at the lesson titled Note: Testing React Apps and watch from there. If you already know everything about testing and you just want to see how tests are implemented in our Master Project, go straight to Testing In Our Master Project!
Enjoy :)
I know we are waiting a long time to write tests, but we are getting there. The next videos will cover a few more topics that you may have to revisit after you have finished this section. Donโt worry if you donโt fully understand them yet. I want to make sure that we have a basic understanding of testing before we dive into coding to finish off this section.
Again, watch the next few videos but donโt get hung up if you donโt understand everything. Come back to them once you are done this section and you will see that they will be a lot clearer for you.
npm i -D jest
const googleDatabase = [
"cats.com",
"souprecipes.com",
"flowers.com",
"animals.com",
"catpictures.com",
"myanimalscats.com",
"ilovecats.com"
];
const googleSearch = (searchInput, db) => {
const matches = db.filter(website => {
return website.includes(searchInput);
});
return matches.length > 3 ? matches.slice(0, 3) : matches;
};
// console.log(googleSearch("cat", googleDatabase));
module.exports = googleSearch;
const googleSearch = require("./script.js");
const dbMock = [
"dog.com",
"cheese.com",
"ratpoison.com",
"ilovedogs.com",
"dogpictures.com",
"disney.com"
];
describe("googleSearch", () => {
it("silly test", () => {
expect("hello").toBe("hello");
});
it("this is a test", () => {
expect(googleSearch("testtest", dbMock)).toEqual([]);
expect(googleSearch("dog", dbMock)).toEqual([
"dog.com",
"ilovedogs.com",
"dogpictures.com"
]);
});
it("work with undefined and null", () => {
expect(googleSearch(undefined, dbMock)).toEqual([]);
expect(googleSearch(null, dbMock)).toEqual([]);
});
it("does not return more than 3 matches", () => {
expect(googleSearch(".com", dbMock).length).toEqual(3);
});
});
const fetch = require("node-fetch");
// const getPeoplePromise = fetch => {
// return fetch("https://swapi.co/api/people")
// .then(res => res.json())
// .then(data => {
// console.log(data);
// return {
// count: data.count,
// results: data.results
// };
// });
// };
const getPeople = async fetch => {
const result = await fetch("https://swapi.co/api/people");
const data = await result.json();
console.log(data);
return {
count: data.count,
results: data.results
};
};
console.log(getPeople(fetch));
module.exports = {
getPeople,
getPeoplePromise
};
const fetch = require("node-fetch");
const swapi = require("./script2");
it("call swapi to get people", done => {
expect.assertions(1);
swapi.getPeople(fetch).then(data => {
expect(data.count).toEqual(87);
done();
});
});
it("call swapi to get people with promise", () => {
expect.assertions(2);
return swapi.getPeoplePromise(fetch).then(data => {
expect(data.count).toEqual(87);
expect(data.results.length).toBeGreaterThan(6);
});
});
Moving forward, you should use the Jest Cheat Sheet to help you along as you write tests if you are coding along in the videos. It will also come in handy at the end of this course where you will have to write tests for our robofriends app.
const fetch = require("node-fetch");
const swapi = require("./script2");
it("call swapi to get people", done => {
expect.assertions(1);
swapi.getPeople(fetch).then(data => {
expect(data.count).toEqual(87);
done();
});
});
it("call swapi to get people with promise", () => {
expect.assertions(2);
return swapi.getPeoplePromise(fetch).then(data => {
expect(data.count).toEqual(87);
expect(data.results.length).toBeGreaterThan(6);
});
});
it("get people return count and results", () => { const mockFetch = jest.fn().mockReturnValue( Promise.resolve({ json: () => Promise.resolve({ count: 87, results: [0, 1, 2, 3, 4, 5] }) }) ); return swapi.getPeoplePromise(mockFetch).then(data => { expect(mockFetch.mock.calls.length).toBe(1); expect(mockFetch).toBeCalledWith("https://swapi.co/api/people"); expect(data.count).toEqual(87); expect(data.results.length).toBeGreaterThan(4); });});
In the next videos we will be using the robofriends-pwa GitHub repo as an example for you. You can use my GitHub repository with all of the code here to get started and follow along as we write tests
npm i --save-dev enzyme enzyme-adapter-react-16
setupTests.js
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
npm test
- To run the test files
npm test -- --coverage
- To see the coverage
import { shallow } from "enzyme";
import React from "react";
import Card from "./Card";
it("expect to render Card component", () => {
const cardComponent = shallow(<Card />);
expect(cardComponent.debug()).toMatchSnapshot();
});
import { shallow } from "enzyme";
import React from "react";
import CardList from "./CardList";
it("expect to render CardList component", () => {
const mockRobots = [
{
id: 1,
name: "John Snow",
username: "JohnJohn",
email: "john@gmail.com"
}
];
const cardComponent = shallow(<CardList robots={mockRobots} />);
expect(cardComponent.debug()).toMatchSnapshot();
});
import { shallow } from "enzyme";
import React from "react";
import CounterButton from "./CounterButton";
it("expect to render CounterButton component", () => {
const mockColor = "red";
const counterButtonComponent = shallow(<CounterButton color={mockColor} />);
expect(counterButtonComponent.debug()).toMatchSnapshot();
});
it("expect to render CounterButton component", () => {
const mockColor = "red";
const counterButtonComponent = shallow(<CounterButton color={mockColor} />);
counterButtonComponent.find("[id='counter']").simulate("click");
counterButtonComponent.find("[id='counter']").simulate("click");
counterButtonComponent.find("[id='counter']").simulate("click");
expect(counterButtonComponent.state()).toEqual({ count: 4 });
expect(counterButtonComponent.props().color).toEqual("red");
});
๐ Quick recap on the testing libraries
๐ Separate the Connected component into a Pure component
import { shallow } from "enzyme";
import React from "react";
import MainPage from "./MainPage";
let MainPageComponent;
beforeEach(() => {
const mockProps = {
onRequestRobots: jest.fn(),
robots: [],
searchField: "",
isPending: false
};
MainPageComponent = shallow(<MainPage {...mockProps} />);
});
it("expect to render MainPage component", () => {
expect(MainPageComponent.debug()).toMatchSnapshot();
});
it("filter robots correctly", () => {
const mockProps2 = {
onRequestRobots: jest.fn(),
robots: [
{
id: "1",
name: "John",
email: "john@email.com"
}
],
searchField: "john",
isPending: false
};
const MainPageComponent2 = shallow(<MainPage {...mockProps2} />);
expect(MainPageComponent2.instance().filteredRobots()).toEqual([
{
id: "1",
name: "John",
email: "john@email.com"
}
]);
});
it("filter robots correctly 2", () => {
const mockProps3 = {
onRequestRobots: jest.fn(),
robots: [
{
id: "1",
name: "John",
email: "john@email.com"
}
],
searchField: "a",
isPending: false
};
const filteredRobots = [];
const MainPageComponent3 = shallow(<MainPage {...mockProps3} />);
expect(MainPageComponent3.instance().filteredRobots()).toEqual(
filteredRobots
);
});
import * as reducers from "./reducers";
describe("searchRobots", () => {
const initialStateSearch = {
searchField: ""
};
it("should return the initial state", () => {
expect(reducers.searchRobots(undefined, {})).toEqual({ searchField: "" });
});
it("should handle CHANGE_SEARCHFIELD", () => {
expect(
reducers.searchRobots(initialStateSearch, {
type: "CHANGE_SEARCHFIELD",
payload: "abc"
})
).toEqual({
searchField: "abc"
});
});
});
describe("requestRobots", () => {
const initialRequestRobots = {
robots: [],
isPending: false
};
it("should return the initial state", () => {
expect(reducers.requestRobots(undefined, {})).toEqual(initialRequestRobots);
});
it("should handle REQUEST_ROBOTS_PENDING", () => {
expect(
reducers.requestRobots(initialRequestRobots, {
type: "REQUEST_ROBOTS_PENDING"
})
).toEqual({
robots: [],
isPending: true
});
});
it("should handle REQUEST_ROBOTS_SUCCESS", () => {
expect(
reducers.requestRobots(initialRequestRobots, {
type: "REQUEST_ROBOTS_SUCCESS",
payload: ["abc"]
})
).toEqual({
robots: ["abc"],
isPending: false
});
});
it("should handle REQUEST_ROBOTS_FAILED", () => {
expect(
reducers.requestRobots(initialRequestRobots, {
type: "REQUEST_ROBOTS_FAILED",
payload: "Oops..! Encountered an Error"
})
).toEqual({
robots: [],
isPending: false,
error: "Oops..! Encountered an Error"
});
});
});
npm i -D redux-mock-store
import * as actions from "./actions";
import { CHANGE_SEARCHFIELD, REQUEST_ROBOTS_PENDING } from "./constants";
import configureMockStore from "redux-mock-store";
import thunkMiddleware from "redux-thunk";
const mockStore = configureMockStore([thunkMiddleware]);
it("should create an action to search robots", () => {
const text = "BayWatch";
expect(actions.setSearchField(text)).toEqual({
type: CHANGE_SEARCHFIELD,
payload: "BayWatch"
});
});
it("should create an action to request robots", () => {
const store = mockStore();
store.dispatch(actions.requestRobots());
const action = store.getActions();
console.log(action);
expect(action[0]).toEqual({ type: REQUEST_ROBOTS_PENDING });
});
๐ Testing Section overall Review
Now that you learned about testing, itโs time to write your own tests now for our Mastery Project!
You can always refer to the github repo in the resources that has tests written for the app (which we have written for you) to see how you might tackle writing tests for certain components, or testing certain features! We have also linked the enzyme documentation in the resources as well for you to refer to, this way you can see what methods you may want to use to test specific features!
One caveat to note, if you want to use the selector based methods in enzyme to target styled-components in a component, just add the .displayName
prop to the styled component where itโs equal to the name of the const. Then you can target that displayName as a string that you pass as the selector value. For example:
// header.styles.jsx
export const OptionLink = styled(Link)`
padding: 10px 15px;
cursor: pointer;
`;
OptionLink.displayName = 'OptionLink';
// Header.test.js
describe('test for OptionLink', () => {
wrapper.find('OptionLink') // <= finds OptionLink styled components in wrapper
});
Good luck!
Webpack from scratch || WebPack Doc || Regex Buddy || Babel Doc || Babel Loader
npm i webpack webpack-cli
package.json
๐ New Build Script
"scripts": {
"build": "webpack --mode production"
},
npm i @babel/core @babel/preset-env @babel/preset-react babel-loader
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
};
.babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
Style Loader || CSS Loader || HTML Webpack Plugin || Webpack Final Lesson Repo
npm i style-loader css-loader html-loader html-webpack-plugin
const HTMLWebpackPlugin = require("html-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.html$/,
use: ["html-loader"]
}
]
},
plugins: [new HTMLWebpackPlugin({ template: "./index.html" })]
};
๐ Import react and react-dom error from Webpack?
npm i
npm start
๐ NOTE : For some unknown reason, npm might have failed to pull those dependencies
๐ Get titles for Section 31
$$(".curriculum-item-link--title--zI5QT").map(title => title.textContent);
Hereโs the truth: I (Andrei) am not the biggest fan of GatsbyJS. The reason is that it involves a lot of configuration and setup. Itโs one of those libraries that feels like youโre learning something completely new instead of just writing Javascript. As you go through these videos, you might find yourself saying โthis is so different than anything I have done before as a React developerโ. Donโt get discouraged. GatsbyJS is a type of library that requires you to go through the documentation and sometimes learn a completely different way of structuring your code which is unique to Gastby. We decided to add this as a bonus to the course, but by no means should you feel let down if you donโt get everything in these videos. Like I said, Gatsby is all about the โGastbyJS wayโ, and if you donโt understand it completely, it wonโt affect you outside of using GatsbyJS.
So focus more on creating a blog as a fun way to end the course instead of understanding every detail. As a professional React developer, you wonโt encounter these problems unless you are working directly with Gatsby.
๐ Gatsby
Gatsby CLI || Gatsby-starter-blog || Gatsby Plugins
npm i -g gatsby-cli
gatsby new gatsby-blog
/ npx gatsby new gatsby-blog
npm run develop
๐ Gatsby starter blog walkthrough of components on how things work
gatsby-config.js
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `@gatsbyjs`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{ resolve: `gatsby-source-filesystem`, options: { name: `images`, path: `${__dirname}/src/markdown-pages`, }, }, `gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
],
}
src/pages/index.js
import React from "react"
import { graphql, Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = ({
data: {
allMarkdownRemark: { totalCount, edges },
},
}) => (
<Layout>
<SEO title="Home" />
<div>
<h1>Navin's Blog</h1>
</div>
{edges.map(
({
node: {
id,
frontmatter: { date, description, title },
excerpt,
},
}) => (
<div key={id}>
<h2>
{title}-{date}
</h2>
<p>{excerpt}</p>
</div>
)
)}
</Layout>
)
export default IndexPage
export const query = graphql`
query {
allMarkdownRemark {
totalCount
edges {
node {
id
frontmatter {
date
description
title
}
excerpt
}
}
}
}
`
๐ Created a node field for Markdown Files
gatsby-node.js
const { createFilePath } = require("gatsby-source-filesystem")
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === "MarkdownRemark") {
const slug = createFilePath({ node, getNode })
createNodeField({
node,
name: "slug",
value: slug,
})
}
}
createPages || createPage || Template Literals
gatsby-node.js
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return graphql(`
{
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`).then(({ data }) => {
data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.field.slug,
template: "",
})
})
})
}
dangerouslysetinnerhtml || path
src/templates/blog-post.js
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
export default ({ data }) => {
const post = data.markdownRemark
return (
<Layout>
<div>
<h2>{post.frontmatter.title}</h2>
<div dangerouslySetInnerHTML={{ __html: post.html }}>{}</div>
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`
styled-components || gatsby-plugin-styled-components || babel-plugin-styled-components || Netlify || Finished Gatsby Blog by Andrei
npm i gatsby-plugin-styled-components styled-components babel-plugin-styled-components
๐ Got all Sub Heading by using the below Script
$$(".curriculum-item-link--title--zI5QT").map(text => text.textContent)
const myArray = [1, 2, 3, 4];
myArray.map(el => el + 1);
// [2, 3, 4, 5]
myArray.map(() => "b");
// ["b", "b", "b", "b"]
Resolved Promise
const newPromise = new Promise((resolve, reject) => {
if (true) {
setTimeout(() => resolve("I resolved Successfully"), 1000);
} else {
reject("Error - Rejecting");
}
});
newPromise.then(res => console.log(res)).catch(err => console.log(err));
// Promise {<pending>}
// I resolved Successfully
Rejected Promise
const newPromise = new Promise((resolve, reject) => {
if (false) {
setTimeout(() => resolve("I resolved Successfully"), 1000);
} else {
reject("Error - Rejecting");
}
});
newPromise.then(res => console.log(res)).catch(err => console.log(err));
// Error - Rejecting
const myArray = [1, 2, 3, 4];
myArray.filter(el => true);
// [1, 2, 3, 4]
myArray.filter(el => false);
// []
myArray.filter(el => el > 2);
// [3, 4]
includes(<value>, <index>)
Basic
const myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
myArray.includes(5);
// true
myArray.includes(10);
// false
myArray.includes(5, 6);
// false
myArray.includes(5, 3);
// true
Why this happens
const myArray = [{ id: 1 }, { id: 2 }, { id: 3 }];
myArray.includes({ id: 1 });
// false
const ob1 = { id: 1 };
const ob2 = { id: 2 };
const ob3 = { id: 3 };
const secArray = [ob1, ob2, ob3];
secArray.includes(ob1);
// true
This behavior is because of the JavaScriptโs difference between Primitive Types and Object Types
Primitve Types
Object Types
- Objects & Arrays
ES6
ES7
๐ Unlike filter(), find() only gives the first element when the condition matches
const myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
myArray.find(el => el === 5);
// 5
const myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
myArray.reduce((acc, currEl) => acc + currEl, 0);
// 45
caching repeatedly requested expressions
const cache = {};
const memoizedfunc = n => {
if (n in cache) {
console.log("Cached");
return cache[n];
} else {
console.log("Not Cached");
cache[n] = n + 80;
return cache[n];
}
};
console.log(memoizedfunc(5));
console.log(memoizedfunc(5));
console.log(memoizedfunc(5));
๐ Function that returns another function utilizing closure to get variables out of the scope and calling them one by one
const currying = a => b => c => a * b * c;
console.log(currying(5)(2)(10));