parent
34e4ef9d00
commit
06364e1901
@ -0,0 +1,122 @@
|
||||
<div align="center">
|
||||
<h1> 30 Days Of React: Fetching Data Using Hooks</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 22](../22_Form_Using_Hooks/22_form_using_hooks.md) | [Day 24>>]()
|
||||
|
||||
![30 Days of React banner](../images/30_days_of_react_banner_day_23.jpg)
|
||||
|
||||
- [Introducing React Hook](#introducing-react-hook)
|
||||
- [Basic Hooks](#basic-hooks)
|
||||
- [State Hook](#state-hook)
|
||||
- [Effect Hook](#effect-hook)
|
||||
- [Context Hook](#context-hook)
|
||||
- [Additional Hook](#additional-hook)
|
||||
- [Exercises](#exercises)
|
||||
- [Exercises: Level 1](#exercises-level-1)
|
||||
|
||||
# Fetching Data Using Hooks
|
||||
|
||||
In the previous sections, you have learned how to fetch data using fetch and axios. In this section , we will use the useEffect hook to fetch data. We can use fetch or axios but I prefer to use axios. In React hooks, you don't have to use componentDidMount life cycle separately to fetch data. The useEffect has incorporate the React life cycle methods(mounting, updating and unmounting). Let's convert the code we wrote on day 18 to React hooks. We need to import the useEffect from react. The useEffect takes to argument that is a callback and an array. If the array is empty it behaves as componentDidMount life cycle where if the array has other properties, it will behave as updating too.
|
||||
|
||||
```js
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import axios from 'axios'
|
||||
import ReactDOM, { findDOMNode } from 'react-dom'
|
||||
|
||||
const Country = ({
|
||||
country: { name, capital, flag, languages, population, currency },
|
||||
}) => {
|
||||
const formattedCapital =
|
||||
capital.length > 0 ? (
|
||||
<>
|
||||
<span>Capital: </span>
|
||||
{capital}
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
const formattedLanguage = languages.length > 1 ? `Languages` : `Language`
|
||||
|
||||
return (
|
||||
<div className='country'>
|
||||
<div className='country_flag'>
|
||||
<img src={flag} alt={name} />
|
||||
</div>
|
||||
<h3 className='country_name'>{name.toUpperCase()}</h3>
|
||||
<div class='country_text'>
|
||||
<p>{formattedCapital}</p>
|
||||
<p>
|
||||
<span>{formattedLanguage}: </span>
|
||||
{languages.map((language) => language.name).join(', ')}
|
||||
</p>
|
||||
<p>
|
||||
<span>Population: </span>
|
||||
{population.toLocaleString()}
|
||||
</p>
|
||||
<p>
|
||||
<span>Currency: </span>
|
||||
{currency}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const App = (props) => {
|
||||
// setting initial state and method to update state
|
||||
const [data, setData] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
const fetchData = async () => {
|
||||
const url = 'https://restcountries.eu/rest/v2/all'
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
setData(data)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='App'>
|
||||
<h1>Fetching Data Using Hook</h1>
|
||||
<h1>Calling API</h1>
|
||||
<div>
|
||||
<p>There are {data.length} countries in the api</p>
|
||||
<div className='countries-wrapper'>
|
||||
{data.map((country) => (
|
||||
<Country country={country} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
ReactDOM.render(<App />, rootElement)
|
||||
```
|
||||
|
||||
# Exercises
|
||||
|
||||
🎉 CONGRATULATIONS ! 🎉
|
||||
|
||||
|
||||
[<< Day 22](../22_Form_Using_Hooks/22_form_using_hooks.md) | [Day 24>>]()
|
@ -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 17
|
||||
|
||||
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>
|
File diff suppressed because it is too large
Load Diff
@ -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,365 @@
|
||||
import React, { useState } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '',
|
||||
label: '-- Select Country--',
|
||||
},
|
||||
{
|
||||
value: 'Finland',
|
||||
label: 'Finland',
|
||||
},
|
||||
{
|
||||
value: 'Sweden',
|
||||
label: 'Sweden',
|
||||
},
|
||||
{
|
||||
value: 'Norway',
|
||||
label: 'Norway',
|
||||
},
|
||||
{
|
||||
value: 'Denmark',
|
||||
label: 'Denmark',
|
||||
},
|
||||
]
|
||||
|
||||
// mapping the options to list(array) of JSX options
|
||||
|
||||
const selectOptions = options.map(({ value, label }) => (
|
||||
<option key={label} value={value}>
|
||||
{' '}
|
||||
{label}
|
||||
</option>
|
||||
))
|
||||
|
||||
const App = (props) => {
|
||||
const initialState = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
title: '',
|
||||
country: '',
|
||||
tel: '',
|
||||
dateOfBirth: '',
|
||||
favoriteColor: '',
|
||||
weight: '',
|
||||
gender: '',
|
||||
file: '',
|
||||
bio: '',
|
||||
skills: {
|
||||
html: false,
|
||||
css: false,
|
||||
javascript: false,
|
||||
},
|
||||
touched: {
|
||||
firstName: false,
|
||||
lastName: false,
|
||||
},
|
||||
}
|
||||
const [formData, setFormData] = useState(initialState)
|
||||
|
||||
const onChange = (e) => {
|
||||
/*
|
||||
we can get the name and value like: e.target.name, e.target.value
|
||||
Wwe can also destructure name and value from e.target
|
||||
const name = e.target.name
|
||||
const value = e.target.value
|
||||
*/
|
||||
const { name, value, type, checked } = e.target
|
||||
/*
|
||||
[variablename] we can make a value stored in a certain variable could be a key for an object, in this case a key for the state
|
||||
*/
|
||||
|
||||
if (type === 'checkbox') {
|
||||
setFormData({
|
||||
...formData,
|
||||
skills: { ...formData.skills, [name]: checked },
|
||||
})
|
||||
} else if (type === 'file') {
|
||||
setFormData({ ...formData, [name]: e.target.files[0] })
|
||||
} else {
|
||||
setFormData({ ...formData, [name]: value })
|
||||
}
|
||||
}
|
||||
const onSubmit = (e) => {
|
||||
/*
|
||||
e.preventDefault()
|
||||
stops the default behavior of form element
|
||||
specifically refreshing of page
|
||||
*/
|
||||
e.preventDefault()
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
title,
|
||||
email,
|
||||
tel,
|
||||
dateOfBirth,
|
||||
favoriteColor,
|
||||
weight,
|
||||
country,
|
||||
gender,
|
||||
bio,
|
||||
file,
|
||||
skills,
|
||||
} = formData
|
||||
|
||||
const formattedSkills = []
|
||||
for (const key in skills) {
|
||||
console.log(key)
|
||||
if (skills[key]) {
|
||||
formattedSkills.push(key.toUpperCase())
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
firstName,
|
||||
lastName,
|
||||
title,
|
||||
email,
|
||||
tel,
|
||||
dateOfBirth,
|
||||
favoriteColor,
|
||||
weight,
|
||||
country,
|
||||
gender,
|
||||
bio,
|
||||
file,
|
||||
skills: formattedSkills,
|
||||
}
|
||||
/*
|
||||
the is the place where we connect backend api
|
||||
to send the data to the database
|
||||
*/
|
||||
console.log(data)
|
||||
}
|
||||
const onBlur = (e) => {
|
||||
const { name } = e.target
|
||||
setFormData({ ...formData, touched: { ...formData.touched, [name]: true } })
|
||||
}
|
||||
const validate = () => {
|
||||
// Object to collect error feedback and to display on the form
|
||||
const errors = {
|
||||
firstName: '',
|
||||
}
|
||||
|
||||
if (
|
||||
(formData.touched.firstName && formData.firstName.length < 3) ||
|
||||
(formData.touched.firstName && formData.firstName.length > 12)
|
||||
) {
|
||||
errors.firstName = 'First name must be between 2 and 12'
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
// accessing the state value by destrutcturing the state
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
title,
|
||||
country,
|
||||
email,
|
||||
tel,
|
||||
dateOfBirth,
|
||||
favoriteColor,
|
||||
weight,
|
||||
gender,
|
||||
bio,
|
||||
} = formData
|
||||
|
||||
const errors = validate()
|
||||
|
||||
return (
|
||||
<div className='App'>
|
||||
<h3>Add Student</h3>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className='row'>
|
||||
<div className='form-group'>
|
||||
<label htmlFor='firstName'>First Name </label>
|
||||
<input
|
||||
type='text'
|
||||
id='firstName'
|
||||
name='firstName'
|
||||
value={firstName}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
placeholder='First Name'
|
||||
/>
|
||||
<br />
|
||||
{errors.firstName && <small>{errors.firstName}</small>}
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label htmlFor='lastName'>Last Name </label>
|
||||
<input
|
||||
type='text'
|
||||
id='lastName'
|
||||
name='lastName'
|
||||
value={lastName}
|
||||
onChange={onChange}
|
||||
placeholder='Last Name'
|
||||
/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label htmlFor='title'>Title </label>
|
||||
<input
|
||||
type='text'
|
||||
id='title'
|
||||
name='title'
|
||||
placeholder='Title'
|
||||
value={title}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label htmlFor='email'>Email </label>
|
||||
<input
|
||||
type='email'
|
||||
id='email'
|
||||
name='email'
|
||||
value={email}
|
||||
onChange={onChange}
|
||||
placeholder='Email'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label htmlFor='tel'>Telephone </label>
|
||||
<input
|
||||
type='tel'
|
||||
id='tel'
|
||||
name='tel'
|
||||
value={tel}
|
||||
onChange={onChange}
|
||||
placeholder='Tel'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label htmlFor='dateOfBirth'>Date of birth </label>
|
||||
<input
|
||||
type='date'
|
||||
id='dateOfBirth'
|
||||
name='dateOfBirth'
|
||||
value={dateOfBirth}
|
||||
onChange={onChange}
|
||||
placeholder='Date of Birth'
|
||||
/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label htmlFor='favoriteColor'>Favorite Color</label>
|
||||
<input
|
||||
type='color'
|
||||
id='color'
|
||||
name='favoriteColor'
|
||||
value={favoriteColor}
|
||||
onChange={onChange}
|
||||
placeholder='Favorite Color'
|
||||
/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label htmlFor='weight'>Weight </label>
|
||||
<input
|
||||
type='number'
|
||||
id='weight'
|
||||
name='weight'
|
||||
value={weight}
|
||||
onChange={onChange}
|
||||
placeholder='Weight in Kg'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor='country'>Country</label> <br />
|
||||
<select
|
||||
name='country'
|
||||
onChange={onChange}
|
||||
id='country'
|
||||
value={country}
|
||||
>
|
||||
{selectOptions}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Gender</p>
|
||||
<div>
|
||||
<input
|
||||
type='radio'
|
||||
id='female'
|
||||
name='gender'
|
||||
value='Female'
|
||||
onChange={onChange}
|
||||
checked={gender === 'Female'}
|
||||
/>
|
||||
<label htmlFor='female'>Female</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
id='male'
|
||||
type='radio'
|
||||
name='gender'
|
||||
value='Male'
|
||||
onChange={onChange}
|
||||
checked={gender === 'Male'}
|
||||
/>
|
||||
<label htmlFor='male'>Male</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
id='other'
|
||||
type='radio'
|
||||
name='gender'
|
||||
value='Other'
|
||||
onChange={onChange}
|
||||
checked={gender === 'Other'}
|
||||
/>
|
||||
<label htmlFor='other'>Other</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Select your skills</p>
|
||||
<div>
|
||||
<input type='checkbox' id='html' name='html' onChange={onChange} />
|
||||
<label htmlFor='html'>HTML</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type='checkbox' id='css' name='css' onChange={onChange} />
|
||||
<label htmlFor='css'>CSS</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type='checkbox'
|
||||
id='javascript'
|
||||
name='javascript'
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label htmlFor='javascript'>JavaScript</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor='bio'>Bio</label> <br />
|
||||
<textarea
|
||||
id='bio'
|
||||
name='bio'
|
||||
value={bio}
|
||||
onChange={onChange}
|
||||
cols='120'
|
||||
rows='10'
|
||||
placeholder='Write about yourself ...'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type='file' name='file' onChange={onChange} />
|
||||
</div>
|
||||
<div>
|
||||
<button>Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
ReactDOM.render(<App />, rootElement)
|
@ -0,0 +1,86 @@
|
||||
/* == 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%;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue