@ -0,0 +1,212 @@
|
||||
<div align="center">
|
||||
<h1> 30 Days Of React: Higher Order Component</h1>
|
||||
<a class="header-badge" target="_blank" href="https://www.linkedin.com/in/asabeneh/">
|
||||
<img src="https://img.shields.io/badge/style--5eba00.svg?label=LinkedIn&logo=linkedin&style=social">
|
||||
</a>
|
||||
<a class="header-badge" target="_blank" href="https://twitter.com/Asabeneh">
|
||||
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/asabeneh?style=social">
|
||||
</a>
|
||||
|
||||
<sub>Author:
|
||||
<a href="https://www.linkedin.com/in/asabeneh/" target="_blank">Asabeneh Yetayeh</a><br>
|
||||
<small> October, 2020</small>
|
||||
</sub>
|
||||
|
||||
</div>
|
||||
|
||||
[<< Day 15](../15_Third_Party_Packages/15_third_party_packages.md) | [Day 17 >>]()
|
||||
|
||||
![30 Days of React banner](../images/30_days_of_react_banner_day_16.jpg)
|
||||
|
||||
- [Higher Order Component](#higher-order-component)
|
||||
- [Exercises](#exercises)
|
||||
- [Exercises: Level 1](#exercises-level-1)
|
||||
- [Exercises: Level 2](#exercises-level-2)
|
||||
- [Exercises: Level 3](#exercises-level-3)
|
||||
|
||||
# Higher Order Component
|
||||
|
||||
The term higher order component is similar to higher order function in JavaScript. In JavaScript, a higher order function is a function that takes another function as a parameter or return another function.
|
||||
|
||||
Similar to higher order function, a higher order component takes a component and return another component.
|
||||
This definition will make sense with examples. Look at the example below for better understand.
|
||||
|
||||
```js
|
||||
// One way of writing a Higher Order Component(HOC)
|
||||
import React from 'react'
|
||||
const higherOrderComponent = (Component) => {
|
||||
return (props) => {
|
||||
return <Component {...props} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Most of the time third party libraries use higher order component. For instance redux, react-router-dom and material-u use higher order component.
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
|
||||
const Button = ({ onClick, text, style }) => {
|
||||
return (
|
||||
<button onClick={onClick} style={style}>
|
||||
{text}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const buttonWithStyle = (CompParam) => {
|
||||
const buttonStyles = {
|
||||
backgroundColor: '#61dbfb',
|
||||
padding: '10px 25px',
|
||||
border: 'none',
|
||||
borderRadius: 5,
|
||||
margin: 3,
|
||||
cursor: 'pointer',
|
||||
fontSize: 18,
|
||||
color: 'white',
|
||||
}
|
||||
return (props) => {
|
||||
return <CompParam {...props} style={buttonStyles} />
|
||||
}
|
||||
}
|
||||
const NewButton = buttonWithSuperPower(Button)
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className='App'>
|
||||
<Button text='No Style' />
|
||||
<NewButton text='Styled Button' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
ReactDOM.render(<App />, rootElement)
|
||||
```
|
||||
|
||||
Let's make the buttonWithStyle higher order take more parameter in addition to the component.
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
|
||||
const Button = ({ onClick, text, style }) => {
|
||||
return (
|
||||
<button onClick={onClick} style={style}>
|
||||
{text}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const buttonWithStyles = (CompParam, bgColor = 'default') => {
|
||||
const colors = [
|
||||
{
|
||||
name: 'default',
|
||||
backgroundColor: '#e7e7e7',
|
||||
color: '#000000',
|
||||
},
|
||||
{
|
||||
name: 'react',
|
||||
backgroundColor: '#61dbfb',
|
||||
color: '#ffffff',
|
||||
},
|
||||
{
|
||||
name: 'success',
|
||||
backgroundColor: '#4CAF50',
|
||||
color: '#ffffff',
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
backgroundColor: '#2196F3',
|
||||
color: '#ffffff',
|
||||
},
|
||||
{
|
||||
name: 'warning',
|
||||
backgroundColor: '#ff9800',
|
||||
color: '#ffffff',
|
||||
},
|
||||
{
|
||||
name: 'danger',
|
||||
backgroundColor: '#f44336',
|
||||
color: '#ffffff',
|
||||
},
|
||||
]
|
||||
const { backgroundColor, color } = colors.find((c) => c.name === bgColor)
|
||||
|
||||
const buttonStyles = {
|
||||
backgroundColor,
|
||||
padding: '10px 45px',
|
||||
border: 'none',
|
||||
borderRadius: 3,
|
||||
margin: 3,
|
||||
cursor: 'pointer',
|
||||
fontSize: '1.25rem',
|
||||
color,
|
||||
}
|
||||
return (props) => {
|
||||
return <CompParam {...props} style={buttonStyles} />
|
||||
}
|
||||
}
|
||||
|
||||
const NewButton = buttonWithSuperPower(Button)
|
||||
const ReactButton = buttonWithSuperPower(Button, 'react')
|
||||
const InfoButton = buttonWithSuperPower(Button, 'info')
|
||||
const SuccessButton = buttonWithSuperPower(Button, 'success')
|
||||
const WarningButton = buttonWithSuperPower(Button, 'warning')
|
||||
const DangerButton = buttonWithSuperPower(Button, 'danger')
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className='App'>
|
||||
<Button text='No Style' onClick={() => alert('I am not styled yet')} />
|
||||
<NewButton
|
||||
text='Styled Button'
|
||||
onClick={() => alert('I am the default style')}
|
||||
/>
|
||||
<ReactButton text='React' onClick={() => alert('I have react color')} />
|
||||
<InfoButton
|
||||
text='Info'
|
||||
onClick={() => alert('I am styled with info color')}
|
||||
/>
|
||||
<SuccessButton text='Success' onClick={() => alert('I am succesful')} />
|
||||
<WarningButton
|
||||
text='Warning'
|
||||
onClick={() => alert('I warn you many times')}
|
||||
/>
|
||||
<DangerButton
|
||||
text='Danger'
|
||||
onClick={() => alert('Oh no, you can not restore it')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
ReactDOM.render(<App />, rootElement)
|
||||
```
|
||||
|
||||
The is above example is one use case of Higher Order Component. However, its use case is is more than just styling simple button. It has enormous use cases, it allow us to reuse component and enhance a component with style and functionality. In the coming sections, we will cover React Router and we will use HOC and you will not be surprised when you see one component wrap another component.
|
||||
|
||||
# Exercises
|
||||
|
||||
## Exercises: Level 1
|
||||
|
||||
1. What is higher order function
|
||||
2. What is Higher Order Component
|
||||
3. What is the difference between higher order function and higher order component?
|
||||
4. A higher order component can allow us to enhance a component. (T or F)
|
||||
|
||||
## Exercises: Level 2
|
||||
|
||||
1. Make a higher order component which can handle all the input type.
|
||||
|
||||
## Exercises: Level 3
|
||||
|
||||
coming
|
||||
|
||||
🎉 CONGRATULATIONS ! 🎉
|
||||
|
||||
[<< Day 15](../15_Third_Party_Packages/15_third_party_packages.md) | [Day 17 >>]()
|
@ -0,0 +1,23 @@
|
||||
# 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*
|
@ -0,0 +1,5 @@
|
||||
# 30 Days of React App: Day 16
|
||||
|
||||
In the project directory, you can run to start the project
|
||||
|
||||
### `npm start`
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "30-days-of-react",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500|Roboto:300,400,500&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
|
||||
<title>30 Days Of React App</title>
|
||||
<style>
|
||||
|
||||
/* == General style === */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
font-family: 'Montserrat';
|
||||
font-weight: 300;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.root {
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-wrapper,
|
||||
.main-wrapper,
|
||||
.footer-wrapper {
|
||||
width: 85%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.header-wrapper,
|
||||
.main-wrapper {
|
||||
padding: 10px;
|
||||
margin: 2px auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 70px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #61dbfb;
|
||||
padding: 25;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 10px;
|
||||
padding-bottom: 60px;
|
||||
/* Height of the footer */
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
/* Height of the footer */
|
||||
background: #6cf;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
}
|
||||
.user-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.user-card > img {
|
||||
border-radius: 50%;
|
||||
width: 14%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,13 @@
|
||||
export const tenHighestPopulation = [
|
||||
{ country: 'World', population: 7693165599 },
|
||||
{ country: 'China', population: 1377422166 },
|
||||
{ country: 'India', population: 1295210000 },
|
||||
{ country: 'United States of America', population: 323947000 },
|
||||
{ country: 'Indonesia', population: 258705000 },
|
||||
{ country: 'Brazil', population: 206135893 },
|
||||
{ country: 'Pakistan', population: 194125062 },
|
||||
{ country: 'Nigeria', population: 186988000 },
|
||||
{ country: 'Bangladesh', population: 161006790 },
|
||||
{ country: 'Russian Federation', population: 146599183 },
|
||||
{ country: 'Japan', population: 126960000 },
|
||||
]
|
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 40 KiB |
@ -0,0 +1,137 @@
|
||||
import React, { Component } from 'react'
|
||||
import axios from '../../../17_React_Router/15_react_router_boilerplate/src/node_modules/axios'
|
||||
import ReactDOM from 'react-dom'
|
||||
import moment from '../../../17_React_Router/15_react_router_boilerplate/src/node_modules/moment'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
TiSocialLinkedinCircular,
|
||||
TiSocialGithubCircular,
|
||||
TiSocialTwitterCircular,
|
||||
} from '../../../17_React_Router/15_react_router_boilerplate/src/node_modules/react-icons/ti'
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: 70px;
|
||||
font-weight: 300;
|
||||
`
|
||||
const SubTitle = styled.h2`
|
||||
font-weight: 300;
|
||||
`
|
||||
|
||||
const Header = styled.header`
|
||||
background-color: #61dbfb;
|
||||
padding: 25;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
console.log('I am the constructor and I will be the first to run.')
|
||||
this.state = {
|
||||
firstName: 'John',
|
||||
data: [],
|
||||
day: 1,
|
||||
congratulate: '',
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const API_URL = 'https://restcountries.eu/rest/v2/all'
|
||||
axios
|
||||
.get(API_URL)
|
||||
.then((response) => {
|
||||
this.setState({
|
||||
data: response.data,
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return { firstName: props.firstName }
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
console.log(nextProps, nextState)
|
||||
console.log(nextState.day)
|
||||
if (nextState.day > 31) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
doChallenge = () => {
|
||||
this.setState({
|
||||
day: this.state.day + 1,
|
||||
})
|
||||
}
|
||||
renderCountries = () => {
|
||||
return this.state.data.map((country) => {
|
||||
const languageOrLanguages =
|
||||
country.languages.length > 1 ? 'Langauges' : 'Language'
|
||||
const formatLanguages = country.languages
|
||||
.map(({ name }) => name)
|
||||
.join(', ')
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{' '}
|
||||
<img src={country.flag} alt={country.name} />{' '}
|
||||
</div>
|
||||
<div>
|
||||
<h1>{country.name}</h1>
|
||||
<p>Capital: {country.capital}</p>
|
||||
<p>
|
||||
{languageOrLanguages}: {formatLanguages}
|
||||
</p>
|
||||
<p>Population: {country.population}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.day == 30) {
|
||||
this.setState({
|
||||
congratulate: 'Congratulations,Challenge has been completed',
|
||||
})
|
||||
}
|
||||
console.log(prevState, prevProps)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='App'>
|
||||
<Header>
|
||||
<Title>30 Days Of React</Title>
|
||||
<h2>Getting Started React</h2>
|
||||
<h3>JavaScript Library</h3>
|
||||
<p>Instructor: Asabeneh Yetayey</p>
|
||||
<small>Oct 15, 2020</small>
|
||||
</Header>
|
||||
<p>This challenge was started {moment('2020-10-01').fromNow()}</p>
|
||||
<p>The challenge will be over in {moment('2020-10-30').fromNow()}</p>
|
||||
<p>Today is {moment(new Date()).format('MMMM DD, YYYY HH:mm')}</p>
|
||||
<h1>React Component Life Cycle</h1>
|
||||
<h1>Calling API</h1>
|
||||
<TiSocialLinkedinCircular />
|
||||
<TiSocialGithubCircular />
|
||||
<TiSocialTwitterCircular />
|
||||
|
||||
<button onClick={this.doChallenge}>Do Challenge</button>
|
||||
<p>Challenge: Day {this.state.day}</p>
|
||||
{this.state.congratulate && <h2>{this.state.congratulate}</h2>}
|
||||
<div>
|
||||
<p>There are {this.state.data.length} countries in the api</p>
|
||||
<div className='countries-wrapper'>{this.renderCountries()}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
ReactDOM.render(<App />, rootElement)
|
@ -0,0 +1,137 @@
|
||||
import React, { Component } from 'react'
|
||||
import axios from '../../../17_React_Router/17_react_router_boilerplate/src/node_modules/axios'
|
||||
import ReactDOM from 'react-dom'
|
||||
import moment from '../../../17_React_Router/17_react_router_boilerplate/src/node_modules/moment'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
TiSocialLinkedinCircular,
|
||||
TiSocialGithubCircular,
|
||||
TiSocialTwitterCircular,
|
||||
} from '../../../17_React_Router/17_react_router_boilerplate/src/node_modules/react-icons/ti'
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: 70px;
|
||||
font-weight: 300;
|
||||
`
|
||||
const SubTitle = styled.h2`
|
||||
font-weight: 300;
|
||||
`
|
||||
|
||||
const Header = styled.header`
|
||||
background-color: #61dbfb;
|
||||
padding: 25;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
console.log('I am the constructor and I will be the first to run.')
|
||||
this.state = {
|
||||
firstName: 'John',
|
||||
data: [],
|
||||
day: 1,
|
||||
congratulate: '',
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const API_URL = 'https://restcountries.eu/rest/v2/all'
|
||||
axios
|
||||
.get(API_URL)
|
||||
.then((response) => {
|
||||
this.setState({
|
||||
data: response.data,
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
}
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return { firstName: props.firstName }
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
console.log(nextProps, nextState)
|
||||
console.log(nextState.day)
|
||||
if (nextState.day > 31) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
doChallenge = () => {
|
||||
this.setState({
|
||||
day: this.state.day + 1,
|
||||
})
|
||||
}
|
||||
renderCountries = () => {
|
||||
return this.state.data.map((country) => {
|
||||
const languageOrLanguages =
|
||||
country.languages.length > 1 ? 'Langauges' : 'Language'
|
||||
const formatLanguages = country.languages
|
||||
.map(({ name }) => name)
|
||||
.join(', ')
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{' '}
|
||||
<img src={country.flag} alt={country.name} />{' '}
|
||||
</div>
|
||||
<div>
|
||||
<h1>{country.name}</h1>
|
||||
<p>Capital: {country.capital}</p>
|
||||
<p>
|
||||
{languageOrLanguages}: {formatLanguages}
|
||||
</p>
|
||||
<p>Population: {country.population}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.day == 30) {
|
||||
this.setState({
|
||||
congratulate: 'Congratulations,Challenge has been completed',
|
||||
})
|
||||
}
|
||||
console.log(prevState, prevProps)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='App'>
|
||||
<Header>
|
||||
<Title>30 Days Of React</Title>
|
||||
<h2>Getting Started React</h2>
|
||||
<h3>JavaScript Library</h3>
|
||||
<p>Instructor: Asabeneh Yetayey</p>
|
||||
<small>Oct 15, 2020</small>
|
||||
</Header>
|
||||
<p>This challenge was started {moment('2020-10-01').fromNow()}</p>
|
||||
<p>The challenge will be over in {moment('2020-10-30').fromNow()}</p>
|
||||
<p>Today is {moment(new Date()).format('MMMM DD, YYYY HH:mm')}</p>
|
||||
<h1>React Component Life Cycle</h1>
|
||||
<h1>Calling API</h1>
|
||||
<TiSocialLinkedinCircular />
|
||||
<TiSocialGithubCircular />
|
||||
<TiSocialTwitterCircular />
|
||||
|
||||
<button onClick={this.doChallenge}>Do Challenge</button>
|
||||
<p>Challenge: Day {this.state.day}</p>
|
||||
{this.state.congratulate && <h2>{this.state.congratulate}</h2>}
|
||||
<div>
|
||||
<p>There are {this.state.data.length} countries in the api</p>
|
||||
<div className='countries-wrapper'>{this.renderCountries()}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
ReactDOM.render(<App />, rootElement)
|
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 113 KiB |