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(...)
,   )
[]
  1. When the component first renders
useEffect(
() => {
}
fetch(url).then(...)
,             )
[props.user]
  1. When the component first renders
  2. When props.user changes
useEffect(
() => {
}
fetch(url).then(...)
,          )
[x, y, z]
  1. When the component first renders
  2. When x changes
  3. When y changes
  4. 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

  1. useEffect is used to run side effects and asynchronous code within our React components
  2. We use the dependency array to control when it runs
  3. useEffect functions always run on first render

CYF: React: Lesson 3 useEffect

By Jack Franklin

CYF: React: Lesson 3 useEffect

  • 723