Enhancing Components with Component Lifecycles
Clone or download
Pull request Compare This branch is 1 commit ahead of sf-sei-5:master.
Latest commit cf50445 Dec 6, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
images Updated the method image since the deprecation of few Jan 3, 2019
react-lifecycle-methods Updates demo code Aug 15, 2019
.DS_Store updated readme Feb 26, 2019
cheatsheet.md updated readme Feb 26, 2019
package-lock.json updated readme Feb 26, 2019
readme.md Update readme.md Dec 6, 2019

readme.md

Event Handlers and the Component Lifecycle in React

Learning Objectives

  • Understand how to use React's lifecycle methods
  • Retrieve data from an API inside of a component
  • Handle events in React
  • Look at the React documentation to learn more

Framing (10 min / 0:10)

So far, we've used react components to build simple applications. We've added state and props and controlled data flow through them (using just the render and setState methods). In order to do more complex things, we'll have to use lifecycle methods.

How do we get data from an API? Well we could drop in an AJAX call to fetch some data, but our component would likely render before the AJAX request finished. Our component would see that our data is undefined and either render a blank/empty component or throw an error.

How would we animate a component? (i.e. a sidebar that usually lives off the page, except for when a hamburger menu is pressed.) We could write some code to animate the position of the sidebar, but how could we guarantee it was running after our Sidebar component's render method had been called?

This lesson will walk us through the Component Lifecycle: hooks that are fired at different stages of a components "life" for solving the problems described above, as well as many others.

Throughout the course of this lesson, we'll build out a simple flashcard app with vocabulary keywords pulled from the Oxford Dictionary API. Our flashcard app will cycle through a set of flashcards, giving us 10 seconds to think of the definition before moving on to the next card.

But first, what is the Component Lifecycle?

The Component Lifecycle (10 min / 0:20)

Components provide several lifecycle methods that you can use to control your application based on the state of the UI.

When you include these methods in the component they will be invoked automatically (because we are extending the React.Component class which has them already defined).

Lifecycle methods are called at specific points in the rendering process. You can use these methods to perform actions based on what's happening on the DOM.

  • componentWillUnmount is called immediately before a component is removed from the DOM.
  • componentDidMount, for example, is called immediately after a component is rendered to the DOM.

What do you use lifecycle methods for?

Making asynchronous requests (ajax calls), binding event listeners to components, animating components (once they've rendered), and optimizing for performance (shouldComponentUpdate).

Why is it called a lifecycle? It's an action that repeats in a specific order.

At a very high level

There are two types of component lifecycle methods:

  • Mounting lifecycle methods. e.g. What happens when the component is created? Was an initial state set? Methods:

    • constructor(props)
    • UNSAFE_componentWillMount()
    • static getDerivedStateFromProps(props, state)
    • render()
    • componentDidMount()
  • Updating lifecycle methods. e.g. Has state changed? Methods:

    • UNSAFE_componentWillReceiveProps()
    • shouldComponentUpdate(nextProps, nextState)
    • UNSAFE_componentWillUpdate()
    • render()
    • getSnapshotBeforeUpdate(prevProps, prevState)
    • componentDidUpdate(prevProps, prevState, snapshot)
  • Unmounting lifecycle method when the component is removed from DOM

    • componentWillUnmount()

The documentation gives good examples of what each method should be used for. Check out the documentation on components!

We do: Exploring the Lifecycle methods (20 min / 0:40)

Let's clone down this repository with a short exercise for exploring the lifecycle methods.

This exercise is a simple, 2 "page" website where each page is a component. We'll be adding the component lifecycle methods to each page-component. As we do consider the following questions:

  • What order are the methods run in? Before or after rendering?
  • How many times is the method invoked?
  • What causes the method to be (re)invoked?

Add the mounting methods to HomePage.js and the update methods to AboutPage.js. console.log something in each method to understand the order.

An Aside: Fetch (10 min / 0:50)

For our first example of working with the component lifecycle methods, we'll be retrieving data from an API using AJAX. AJAX calls are asynchronous, so we have to be mindful of how long our request will take and when our components will render.

We're going to use a module named fetch to make our calls. Fetch is commonly used with React (and other frontend JS frameworks) to send HTTP requests to an API.

To use Fetch to query an API at a given url endpoint:

fetch('url')
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.log(error)
  })

Flashcards (90 min / 2:30)

As we dive deeper in to each of the component lifecycle methods and what they're used for, we'll work through the following exercise to create a simple flashcards app.

The starter code for this exercise can be found here.

Let's go ahead and clone the repository:

$ git clone https://git.generalassemb.ly/SF-WDI/flashcards
$ npm install
$ npm start

The app we're going to build will pull characters from a dictionary API and create a flashcard for each word and definition`. The app will then cycle through each word, giving the user 10 seconds to think of the definition before moving on to the next card.

The solution code is here

We Do: Adding the Flashcard Container

Use Axios to query the dictionary API

Solution
// FlashcardContainer.js

class FlashcardContainer extends Component {
  state = {
    flashcards: [],
    currentIndex: 0,
  };

  componentDidMount() {
    fetch(CLIENT_URL)
      .then((response) => response.json())
      .then((data) => {
        console.log(data)
        this.setState({flashcards: data})
      })
	    .catch(err => console.log(err));
  }

  render() {
    let detail = this.state.flashcards[this.state.currentIndex];
    let card;

    if(detail) {
      card = <Flashcard detail={detail}/>;
    }

    return (
      <div>
        {card}
      </div>
    )
  }
}

The componentDidMount method is called once, immediately after your component is rendered to the DOM. If you want to make an AJAX request when your component first renders, this is where to do it (not in the constructor, or in componentWillMount). componentWillMount shouldn't be used for server requests because it may be invoked multiple times before render. Side effects should be avoided in the constructor, and so server requests shouldn't be made there.

Add an event listener to switch between cards

Solution
//FlashcardContainer.js

class FlashcardContainer extends Component {
  state = {
      flashcards: [],
      currentIndex: 0,
    };

  // increment currentIndex
  next = () => {
    let nextIndex = (this.state.currentIndex + 1) === this.state.flashcards.length
      ? this.state.currentIndex
      : this.state.currentIndex + 1;

    this.setState({currentIndex: nextIndex});
  }

  // decremement currentIndex
  prev = () => {
    let prevIndex = (this.state.currentIndex - 1) < 0
      ? 0
      : (this.state.currentIndex - 1);

    this.setState({currentIndex: prevIndex});
  }

  // callback to be used in the event listener below
  handleKeyUp = (event) => {
    if (event.keyCode === 39) this.next();
    if (event.keyCode === 37) this.prev();
  }

  componentDidMount () {
    window.addEventListener('keyup', this.handleKeyUp);

    fetch(CLIENT_URL)
      .then((response) => response.json())
      .then((data) => {
        console.log(data)
        this.setState({flashcards: data})
      })
	    .catch(err => console.log(err));
  }

  render() {
    let detail = this.state.flashcards[this.state.currentIndex];
    let card;

    if(detail) {
      card = <Flashcard detail={detail}/>;
    }

    return (
      <div>
        {card}
      </div>
    )
  }
}

In this example, we have added a constructor that has a few initial state properties. We also add an event listener that switches to the next card on right arrow press. Finally, we add an API pull to fetch the dynamic data we will use for the app.

We Do: Adding the Definition Component

Now that we have our flashcard displayed, lets add their definitions as well.

For this, we will use a pure functional component -- or a component created by a function instead of a class -- and then change its style dynamically based on its index.

Solution
//Definition.js

import React from 'react';

let Definition = (props) => {

	return (
		<div className="">
			<p>{props.def}</p>
		</div>
	)
}

export default Definition;
// Flashcard.js
...
class Flashcard extends Component {
  render() {
    let defs = this.props.detail.definitions[0].definitions;

    return (
      <div class="card">
        <p>{this.props.detail.word}</p>
        { defs.map(def => <Definition def={def} /> )}
      </div>
    )
  }
}
...

You Do: Adding a Timer to the Flashcard

Add a timer to the Flashcard Detail component.

  • Initialize the timer to have 10 seconds on it
  • Every second the same flashcard is still on the board, remove a second from it

hint: use setInterval())

  • When the timer reaches zero, switch to the next card

hint: where did you define the next() method? How can you access it from Flashcard.js?

  • When the Flashcard component receives a new card, restart the timer

You Do: Show or Hide the Definition

Add a button that, when clicked, toggles whether or not the definition card is displayed on the page.

Event Handlers

Throughout the last few classes, you've seen a couple event handlers. These look similar to including them inline in HTML.

In the React Intro class, we went over how we are not actually interacting directly with the DOM when we write React code, rather, we are interacting with the virtualDOM. Because of this, when we call an event listener, we use SyntheticEvent's instead of the usual event objects we are used to dealing with in JQuery events. We still get all of the same properties attached to them, and some additional ones. Because of this, the traditional documentation on event handlers is sometimes not accurate. Instead, use the React event handling documentation found here. Spend a few minutes looking at the different events on listed here.