import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Click me</button>
</div>
);
}
export default Counter;
The life of a component
The life of a component
The life of a component
"Click me" clicked
setCount(count + 1)
React re-renders
App() called again
You clicked 1 times!
When React re-renders, it calls your component's function again.
Fetching data in React components
const photoApiUrl =
"https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=2015-6-3&api_key=DEMO_KEY";
function MartianPhotoFetcher() {
let imgSrc = null;
fetch(photoApiUrl)
.then((res) => res.json())
.then((data) => {
imgSrc = data.photos[0].img_src;
});
return (
<div>
Picture:
<img src={imgSrc} />
</div>
);
}
React starts rendering
MartianPhotoFetcher()
return <div>...</div>
React finishes rendering
fetch(url)
fetch finished
React starts rendering
MartianPhotoFetcher()
return <div>...</div>
React finishes rendering
fetch(url)
fetch finished
React starts rendering
MartianPhotoFetcher()
return <div>...</div>
React finishes rendering
fetch(url)
fetch finished
Side effect
useEffect
useEffect(
() => {
}
fetch(url).then(...)
, )
[]
(we're going to come back to this, but for now just ignore it and trust me that you need it!)
function MartianPhotoFetcher() {
const [imgSrc, setImgSrc] = useState(null);
useEffect(() => {
fetch(photoApiUrl)
.then((res) => res.json())
.then((data) => {
setImgSrc(data.photos[0].img_src);
});
}, []);
return (
<div>
Picture:
<img src={imgSrc} />
</div>
);
}
React starts rendering
MartianPhotoFetcher()
return <div>...</div>
React finishes rendering
fetch(url)
fetch finished
useEffect
React starts rendering
In our code, useEffect only runs once and doesn't run again when our component re-renders
useEffect(
() => {
}
fetch(url).then(...)
, )
[]
and that's all because of this empty array
which we're going to talk more about later
Exercise A [15 mins]
1. Open the pokedex React application again. |
2. Create a new file src/PokemonMoves.js, and copy/paste the code from this CodeSandbox. |
3. Render the PokemonMoves component inside the App component (underneath CaughtPokemon). |
4. Take a few minutes to read the code in the new PokemonMoves component. Discuss with another student why you think it doesn't work. |
5. Create a new state variable called pokemonData and initialise it to null.
Click here if you are stuck. |
6. Now add a useEffect call, but leave the callback function empty for now. Make sure you remember to add the empty array after the callback function. |
7. Inside the useEffect callback, call the fetch function with this URL: https://pokeapi.co/api/v2/pokemon/1/. |
8. Add a .then handler into the fetch function (remember this needs to come immediately after the fetch call) which converts the response from JSON (hint: .then(res => res.json())). |
9. Add a second .then handler after the one we just added, where the callback function will receive an argument called data. |
11. Within the second .then callback function, log out the data that we just received (hint: console.log(data)). Inspect the data in the dev tools console. Is there any interesting data? (Hint: think about what the component is trying to render). |
12. Still within the second .then callback, set the pokemonData state to be the data object we received from the API. |
13. What happens in your browser? Do you understand why? If not, discuss it with another student. If you are both stuck, ask a Teaching Assistant. |
Conditional rendering in React
function Counter() {
const [count, setCount] = useState(0);
if (count === 0) {
return <p>No count!</p>
} else {
return <p>Count is {count}</p>
}
}
function Counter() {
const [count, setCount] = useState(0);
return count === 0 ? <p>No Count!</p> : <p>Count is {count}</p>;
}
Ternary operator
if(
count === 0
) {
} else {
}
return <p>No Count</p>
return <p>{count}</p>
count === 0
?
:
<p>No Count</p>
<p>{count}</p>
cond
?
:
TrueCase
FalseCase
import React, { useState, useEffect } from "react";
function MartianPhotoFetcher() {
const [imgSrc, setimgSrc] = useState(null);
useEffect(...)
return imgSrc && <img src={imgSrc} />;
}
return ( imgSrc
&&
<img ... />
)
return <p>
&&
<img ... />
</p>;
{img
}
Exercise B [5 mins]
In this exercise, we'll change the PokemonMoves component to use a ternary operator. Your app should still look the same in your browser as Exercise A. |
1. Open the pokedex application and the src/PokemonMoves.js file. |
2. Change the if / else statement in your JSX to use the ternary operator (condition ? outputIfTrue : outputIfFalse) instead. |
The circle of life
function App() {
const [user, setUser] = useState('jackfranklin');
function loadJackData() {
setUser('jackfranklin');
}
function loadBillGatesData() {
setUser('billgates');
}
return (
<div>
<button onClick={loadJackData}>Load Jack's data</button>
<button onClick={loadBillGatesData}>Load Bill's data</button>
<GitHubData user={user} />
</div>
)
}
function GitHubData(props) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(githubUrl + props.user)
.then((r) => r.json())
.then((data) => {
setData(data);
});
}, []);
return (
<div>
{data && (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
App
user
GitHubData user={user}
data
fetch(url)
useEffect
setData
re-render GithubData
App()
<GitHub user={'jackfranklin'} />
useEffect runs
state: user: 'jackfranklin'
React re-renders
App()
<GitHub user={'jackfranklin'} />
state: user: 'billgates'
Click "Load Bill's data"
<GitHub user={'billgates'} />
useEffect...does not run
Exercise C and D [5 mins]
https://codepen.io/jackfranklin/pen/GRrZYQa?editors=0110
1. Did you spot where the bug was? Discuss with a group of 2 - 3 students where you think the bug is. |
2. Report back to the rest of the class where you think the bug happened. |
useEffect(
() => {
}
fetch(url).then(...)
, )
[]
REMEMBER THIS?!?!
useEffect(
() => {
}
fetch(url).then(...)
, )
[props.user]
The array you pass to useEffect defines its dependencies
Here a dependency is a "thing that should cause the useEffect to re-run"
useEffect(
() => {
}
fetch(url).then(...)
, )
[]
- When the component first renders
useEffect(
() => {
}
fetch(url).then(...)
, )
[props.user]
- When the component first renders
- When props.user changes
useEffect(
() => {
}
fetch(url).then(...)
, )
[x, y, z]
- When the component first renders
- When x changes
- When y changes
- When z changes
useEffect(
() => {
}
fetch(url).then(...)
)
What does this do?
useEffect(
() => {
}
fetch(url).then(...)
)
This runs every single time the component updates
useEffect(
() => {
}
setCount(Math.random())
)
This will run infinitely!
Component renders
useEffect runs
setCount(Math.random())
Component updates
Make sure you define your dependencies correctly.
If in doubt, start with an empty array, which at least means you won't run infinitely!
FIRST RENDER
useEffect
should run useEffect?
FIRST RENDER
useEffect
should run useEffect?
RE-RENDERS
useEffect
dependencies have changed
no dependencies, or dependencies have not changed
useEffect(() => {
fetch(githubUrl + props.user)
.then((r) => r.json())
.then((data) => {
setData(data);
});
}, [props.user]);
The fix!
Exercise E [15 mins]
1. Open the pokedex React application. |
2. Create a new file src/PokemonMovesSelector.js. Copy/paste the code from this CodeSandbox into the new file. |
3. Open src/App.js and replace the PokemonMoves component with the PokemonMovesSelector component (remember to update the import too!) |
4. Take a few minutes to read what the PokemonMovesSelector component does. If you have questions, ask a Teaching Assistant to help. We won't need to make any more changes to this component. |
5. Open the PokemonMoves component again. Change the URL to use backticks (`...`) instead of double-quotes ("..."). Then replace the hard-coded number 1 with ${props.pokemonId}. What will this do?
Click here if you don't know |
6. Open your browser and find where the PokemonMovesSelector component is rendered. Before you click the buttons, think about what you expect will happen. Then click the "Fetch Bulbasaur" button to find out what actually happens. |
7. Refresh the page. What happens now if you click the "Fetch Bulbasaur" button, then click the "Fetch Charmander" button? Is this what you expected? Discuss with another student why this happens. |
8. Fix the bug by adding props.pokemonId to the useEffect dependencies array in PokemonMoves. Remember that you can test if the bug still happens by refreshing the page, clicking one of the buttons, then the other button. |
ESLint and React Hooks
npmjs.com/package/eslint-plugin-react-hooks
useEffect
useEffect
- useEffect is used to run side effects and asynchronous code within our React components
- We use the dependency array to control when it runs
- useEffect functions always run on first render
CYF: React: Lesson 3 useEffect
By Jack Franklin
CYF: React: Lesson 3 useEffect
- 847