Skip to content

Instantly share code, notes, and snippets.

@leahgarrett
Last active June 26, 2019 02:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leahgarrett/b2744a1abebc7d3600a7414393837671 to your computer and use it in GitHub Desktop.
Save leahgarrett/b2744a1abebc7d3600a7414393837671 to your computer and use it in GitHub Desktop.
Life cycle and public API

Life cycle and public API

Today we will look a React life cycle methods and using a public API.

Create the app we will be working on today

Create the app
npx create-react-app pokedex

Change into the app folder
cd pokedex

Install dependencies
npm install

Run the app
npm start

See a spinning logo?

Back at terminal lets open Visual Studio Code
code .

Remove parts of the code we don't need:

/src/App.js

import React from 'react';
import './App.css';

function App() {
  return (
    <div>
    </div>
  );
}

export default App;

Convert this to make App a class component: /src/App.js

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    return (
      <div>
      </div>
    );
  }
}

export default App;

componentDidMount

All life cycle methods are called automatically by React. render is a life cycle method. If we don't implement our own version React will run the default behavior.

Mount is when the JSX has been converted into HTML and has been inserted into the DOM.

Another commonly used life cycle method is componentDidMount. componentDidMount is called after constructor (if there is any) and after first render. This method is only called once, when the component is first mounted to the DOM.

Add the following code and watch the console to see which will run first.

/src/App.js

class App extends Component {
  componentDidMount() {
    console.log('App is running componentDidMount');
  }
  
  render() {
    console.log('App is running render');
    return (
      <div>
        Hello
      </div>
    );
  }

}

componentDidUpdate

When would you expect the componentDidUpdate method to be called?

/src/App.js

class App extends Component {
  componentDidMount() {
    console.log('App is running componentDidMount');
  }

  componentDidUpdate() {
    console.log('App is running componentDidUpdate');
  }

  render() {
    console.log('App is running render');
    return (
      <div>
        Hello
      </div>
    );
  }
}

componentDidUpdate is called immediately after updating occurs. It is not called from the initial render. If we change anything componentDidUpdate will be called.


componentWillUnmount

When would you expect componentWillUnmount to be called?

/src/App.js

class App extends Component {
  componentDidMount() {
    console.log('App is running componentDidMount');
  }

  componentDidUpdate() {
    console.log('App is running componentDidUpdate');
  }

  componentWillUnmount() {
    console.log('App is running componentWillUnmount');
  }

  render() {
    console.log('App is running render');
    return (
      <div>
        Hello
      </div>
    );
  }
}

The opposite of componentWillMount, componentWillUnmount is called as the component is removed from the DOM. This can be useful for clean-up code eg: clear interval timers, unresolved promised, cancel anything pending.

It is not being triggered in this example but in a situation where a component is conditionally displayed will trigger it.
For example:
{checkPassed ? <Greeting /> : <Notification message='Error' />}


shouldComponentUpdate

When would you expect shouldComponentUpdate to be called?

/src/App.js

  shouldComponentUpdate(newProps, newState) {
    console.log('App is running shouldComponentUpdate');

    return true;
  }

By default shouldComponentUpdate will return true. We can implement our own implementation that will check if this.state is equivalent to newState or this.props is equivalent to newProps.


React Life cycle methods overview

https://cdn-images-1.medium.com/max/800/1*U13Mlxz_ktcajaeJCyYkwg.png http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/


Implementing our API call

What would be the best life cycle method for us to make our API call?

/src/App.js

  componentDidMount() {
    console.log('App is running componentDidMount');
    const url = 'https://pokeapi.co/api/v2/pokemon';
    fetch(url)
    .then(resp => resp.json())
    .then(json => {
      console.log(json);
    })
  }

The results includes invalid pokemon. So we will splice the array to get the one we want. /src/App.js

  class App extends Component {
  componentDidMount() {
    console.log('App is running componentDidMount');
    const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=151';
    fetch(url)
    .then(resp => resp.json())
    .then(json => {
      const pokemon = json.results
      console.log(pokemon);
    })
  }

/src/App.js

class App extends Component {
  state = {
    pokemon: []
  }
  componentDidMount() {
    console.log('App is running componentDidMount');
    const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=151';
    fetch(url)
    .then(resp => resp.json())
    .then(json => {
      const pokemon = json.results;
      this.setState({ pokemon: pokemon });
    })
  }

  componentDidUpdate() {
    console.log('App is running componentDidUpdate');
  }

  componentWillUnmount() {
    console.log('App is running componentWillUnmount');
  }


  shouldComponentUpdate(newProps, newState) {
    console.log('App is running shouldComponentUpdate');

    return true;
  }

  render() {
    console.log(`App is running render ${this.state.pokemon.length}`);
    return (
      <div>
        We have {this.state.pokemon.length} pokemon
      </div>
    );
  }
}

Console output shows the first time the component was rendered the pokemon were not loaded.

Navigated to http://localhost:3000/
App.js:35 App is running render 0
App.js:9 App is running componentDidMount
App.js:29 App is running shouldComponentUpdate
App.js:35 App is running render 151
App.js:20 App is running componentDidUpdate

We can add extra console logs to shouldComponentUpdate to see what is happening.

/src/App.js

  shouldComponentUpdate(newProps, newState) {
    console.log('App is running shouldComponentUpdate');

    console.log(newProps)
    console.log(newState)
    return true;
  }

Displaying the results from the API call

The data we get back from the pokemon API is being saved in the state. We can access this data in the render method to display each of the pokemon.

/src/App.js

  render() {
    console.log(`App is running render ${this.state.pokemon.length}`);
    return (
      <div>
        We have {this.state.pokemon.length} pokemon
        { this.state.pokemon.map(poke => <h2>{poke.name}</h2>) }
      </div>
    );
  }

This gives us a warning.
index.js:1375 Warning: Each child in a list should have a unique "key" prop.

We can fix this by using the index on the map.
Could we have used something else for the key?

/src/App.js

  render() {
    console.log(`App is running render ${this.state.pokemon.length}`);
    return (
      <div>
        We have {this.state.pokemon.length} pokemon
        { this.state.pokemon.map((poke, index) => <h2 key={index}>{poke.name}</h2>) }
      </div>
    );
  }

/src/App.js

  render() {
    console.log(`App is running render ${this.state.pokemon.length}`);
    return (
      <div>
        We have {this.state.pokemon.length} pokemon
        <PokeInfo />
        { this.state.pokemon.map((poke, index) => <h2 key={index}>{poke.name}</h2>) }
      </div>
    );
  }

At the top of the file after the React import add the import for our new component: /src/App.js

import PokeInfo from './components/PokeInfo';

Create a minimal component to stop the errors from displaying:

/src/components/PokeInfo.js

import React, { Component} from 'react';

class PokeInfo extends Component {
  render() {
    return (
      <div>
          <h3>More Information</h3>
      </div>
    );
  }
}

export default PokeInfo;

In our App component we will add an onClick event to our pokemon h2 heading. The onClick is epecting a reference to a method we want to run. We will add it as an anonymous function which is going to be called when we click. This means we can pass a value to handlePokemonInfo.

In handlePokemonInfo we will console log the poke to confirm we have the correct value.

/src/App.js

  handlePokemonInfo(poke) {
    console.log(poke)
  }

  render() {
    console.log(`App is running render ${this.state.pokemon.length}`);
    return (
      <div>
        We have {this.state.pokemon.length} pokemon
        <PokeInfo />
        { this.state.pokemon.map((poke, index) => <h2 key={index} onClick={() => this.handlePokemonInfo(poke)}>{poke.name}</h2>) }
      </div>
    );
  }

Now we need to make the App component kn ow ab out which pokemon was selected.

We will add a new field to the state to keep track of the select pokemon: /src/App.js

  state = {
    pokemon: [],
    selected: null
  }

We will update the code in the handlePokemonInfo to set the selected pokemon the state when it is clicked. /src/App.js

  handlePokemonInfo(poke) {
    this.setState({ selected: poke })
  }

We can see all the life cycle methods that are being called each time we are updating the state.

App is running shouldComponentUpdate
App.js:33 {}
App.js:34 {pokemon: Array(151), selected: {…}}
App.js:43 App is running render 151
App.js:22 App is running componentDidUpdate

Now we have the selected pokemon in the state we want to pass it down to the PokeInfo component via props. /src/App.js

  render() {
    console.log(`App is running render ${this.state.pokemon.length}`);
    return (
      <div>
        We have {this.state.pokemon.length} pokemon
        <PokeInfo pokemon={this.state.selected} />
        { this.state.pokemon.map((poke, index) => <h2 key={index} onClick={() => this.handlePokemonInfo(poke)}>{poke.name}</h2>) }
      </div>
    );
  }

We want to load the pokemon details in our PokeInfo component. Last time we used the life cycle method componentDidMount.

Let's add some console log's to our PokeInfo component.

/src/components/PokeInfo.js

class PokeInfo extends Component {
  componentDidMount() {
    console.log('POKEINFO DID MOUNT')
  }
  render() {
    console.log(this.props)
    return (
      <div>
          <h3>More Information</h3>
      </div>
    );
  }
}

In render we log the props. We can see that the props are null at first then change once we click on a pokemon heading.

We use CAPS for the console log message in componentDidMount. We can see that it gets called once. Not every time the props are updated.

Let's try componentDidUpdate

/src/components/PokeInfo.js

  componentDidUpdate() {
    console.log('POKEINFO DID UPDATE')
  }

This method is being called every time we click on a pokemon heading.

We will add thew fetch for details to this method. We can look at the props we have. We have a url that we will need to use to call for more details.

/src/components/PokeInfo.js

  componentDidUpdate() {
    console.log('POKEINFO DID UPDATE')
    const { url } = this.props.pokemon;
    fetch(url)
    .then(resp => resp.json())
    .then(json => {
      console.log(json);
    })
  }

We get an error:
TypeError: Cannot read property 'url' of null

We shouldn't update is we don't have the details. We can check to see if this.props.pokemon exists before trying to access the url.

/src/components/PokeInfo.js

  componentDidUpdate() {
    console.log('POKEINFO DID UPDATE')
    if (this.props.pokemon) {
      const { url } = this.props.pokemon;
      fetch(url)
      .then(resp => resp.json())
      .then(json => {
        console.log(json);
      })
    }
  }

Great. The error has been fixed.

Looking at the console log we see that the json returned contains the poke we want to render. So we will set the state with that value. /src/components/PokeInfo.js

  componentDidUpdate() {
    console.log('POKEINFO DID UPDATE')
    if (this.props.pokemon) {
      const { url } = this.props.pokemon;
      fetch(url)
      .then(resp => resp.json())
      .then(json => {
        this.setState({ poke: json })
      })
    }
  }

We will need to set the initial state of the poke object to be an empty object. /src/components/PokeInfo.js

  state = {
    poke: null
  }

Now we have the values, let's render them. Checking the console log we can see the values returned in the poke. We can use object destructuring to access them in the render method.

/src/components/PokeInfo.js

  render() {
    const { id, name, height, weight } = this.state.poke;
    return (
    <div>
        <h3>More Information</h3>
        <h4>{id}: {name}</h4>
        <p>Height: {height} | Weight: {weight} </p>
    </div>
    );
  }

We get null object errors. Lets check to see if we have anything to display first. This will prevent the null object errors and stop the 'More Information' heading being displayed too early. /src/components/PokeInfo.js

  render() {
    if (this.state.poke) {
      const { id, name, height, weight } = this.state.poke;
      return (
        <div>
            <h3>More Information</h3>
            <h4>{id}: {name}</h4>
            <p>Height: {height} | Weight: {weight} </p>
        </div>
      );
    }
    return null
  }

Looking at console log we see that there are a lot of updates being called. If we wait long enough the stack will over flow.

Lets add some extra checks and diagnostics to work out why this is happening.

/src/components/PokeInfo.js

  loadPokemon = () => {
    console.log('loading data')
    const { url } = this.props.pokemon;
    fetch(url)
    .then(resp => resp.json())
    .then(json => {
      this.setState({ poke: json })
    })
  }
  componentDidUpdate(prevProps) {
    console.log('POKEINFO DID UPDATE')
    console.log(this.props.pokemon)
    console.log(prevProps.pokemon)
    const checkPrevPropsExist = (prevProps != null && prevProps.pokemon != null);
    const checkCurrentPropsExist = (this.props.pokemon != null)
    console.log(`checkPrevPropsExist ${checkPrevPropsExist}`)
    console.log(`checkCurrentPropsExist ${checkCurrentPropsExist}`)
    if (checkCurrentPropsExist &&
      checkPrevPropsExist) {
        const checkSelectedPokeDifferent = (prevProps.pokemon.name !== this.props.pokemon.name);
        console.log(`prevProps.pokemon.id ${prevProps.pokemon.name}`)
        console.log(`this.props.pokemon.id ${this.props.pokemon.name}`)
        console.log(`checkSelectedPokeDifferent ${checkSelectedPokeDifferent}`)
        if (  checkSelectedPokeDifferent) {
          this.loadPokemon();
        }
      }
  }

The constant updates are no longer happening but the first click is not updating properly.

Completely resolving this issue is left as a challenge.


Challenge

Display extra details for the pokemon in the PokeInfo component. Display:

  • forms
  • moves
  • species
  • stats

Beast

Use github API to display your projects:
API docs:
https://developer.github.com/v3/users/
Example:
https://api.github.com/users/leahgarrett/repos


Further Reading

https://reactjs.org/docs/state-and-lifecycle.html https://reactjs.org/docs/faq-state.html

https://www.freecodecamp.org/news/these-are-the-concepts-you-should-know-in-react-js-after-you-learn-the-basics-ee1d2f4b8030/

https://reactjs.org/tutorial/tutorial.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment