Linguine Code

Understanding React componentDidMount and how it works

When I created my first React component, I started injecting fetch calls before the class Component, and even inside the the render() method.

This caused really weird side-effects on my application, which caused me to grunt.

During my research, I came across a React lifecycle called componentDidMount().

It turns out, injecting my data fetch calls inside componentDidMount() was the answer to my problem, but it brought up new questions.

  • What is React componentDidMount()?
  • Does componentDidMount() only run once?
  • Is componentDidMount() a good lifecycle to fetch data?
  • Can I use async/await on componentDidMount()?

What is componentDidMount()

componentDidMount() is a hook that gets invoked right after a React component has been mounted aka after the first render() lifecycle.


class App extends React.Component {

  componentDidMount() {
    // Runs after the first render() lifecycle
  }

  render() {
    console.log('Render lifecycle')
    return <h1>Hello</h1>;
  }
}

The example above shows a classical approach to access componentDidMount(). Here’s an example with a functional component.


function App() {
  React.useEffect(() => {
    // Runs after the first render() lifecycle
  }, []);

  return <h1>Hello</h1>;
}

The code above uses React.useEffect(), and passes an empty array to emulate the componentDidMount() lifecycle.

Does componentDidMount() only run once?

The answer to that is yes, and no. Huh??


class App extends React.Component {

  state = {
    foo: false,
  };

  componentDidMount() {
    console.log('componentDidMount() lifecycle');

    // Trigger update
    this.setState({ foo: !this.state.foo });
  }

  render() {
    console.log('Render lifecycle')
    return <h1>Hello</h1>
  }
}

Look at the example above. When React looks at this code, it’s going to first render the component and you will see the words “Hello” printed on the screen.

Right after that paint, it will trigger the componentDidMount() lifecycle, and check if that component has componentDidMount() method to run any side effects the developer wants.

In my componentDidMount() method, I’m telling React to update the state of the component.

this.state.foo went from false to true.

When you run that code, in your web console, you should see the following


Render lifecycle
componentDidMount() lifecycle
Render lifecycle

In this case componentDidMount() will ONLY run once.

Let’s look at another example where a React component only triggers componentDidMount() once.


class ChildComp extends React.Component {

  componentDidMount() {
    console.log('componentDidMount() lifecycle')
  }

  render() {
    console.log('render() lifecycle')
    return <h1>{this.props.number} times</h1>;
  }
}

class App extends React.Component {

  state = {
    num: 0,
  };

  handleClick() {
    this.setState(state => ({ num: state.num + 1 }));
  }

  render() {
    return (
      <>
        <button onClick={this.handleClick.bind(this)}>Increment</button>
        <ChildComp number={this.state.num} />
      </>
    )
  }
}

In the example above I’m using console.log() to print the render and component did mount lifecycle triggers.

When you click the increment button, this is what you should see in the console.


render() lifecycle 
componentDidMount() lifecycle 
render() lifecycle 
render() lifecycle 
render() lifecycle 
render() lifecycle

Let’s look at an example of how componentDidMount() may trigger more than once.

I’m going to grab the same code from the previous example, and just add a new property to the <ChildComp /> component.


<ChildComp key={this.state.num} number={this.state.num} />

When you click the increment button, you should see the following output.


render() lifecycle 
componentDidMount() lifecycle 
render() lifecycle 
componentDidMount() lifecycle

By adding the prop key, React will keep an eye on that component, and force a componentWillUnmount if the value changes.

The <ChildComp /> will re-mount and trigger componentDidMount().

Is componentDidMount() a good lifecycle to fetch data?

Absolutely!

My problem when I started learning React lifecycles was, where do I place API calls in my React component when it first loads.

It turns out, you can’t just stick it anywhere. Otherwise it causes really odd, inconsistency issues with your React application.

You can either place your data fetch calls on an event listener, such as click. Or if you need to grab initial data when the component mounts, do it inside the componentDidMount() method.

Other lifecycles like, componentDidUpdate(), componentWillMount(), render(), can be triggered multiple times.

This may cause you to ping your API unnecessary amount of times.


componentDidMount() {
  // initial data fetch() calls here
  fetch('https://jsonplaceholder.typicode.com/posts')
    .then(res => res.json())
    .then(data => this.setState({posts: data}))
    .catch(err => console.error(err));
}

Is it safe to use Async/Await with componentDidMount()

The answer is yes! Because componentDidMount() only triggers once, I don’t see any issues, nor have I experienced any problems by making that method a promise.


async componentDidMount() {
  try {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts');
    this.setState({posts: await res.json()})
  } catch (e) {
    console.error(e);
  }
}

Conclusion

Just to sum up what was covered.

  • componentDidMount() runs after the initial render only once
  • componentDidMount() may run multiple times if the component key prop value keeps changing
  • This is the best hook to run any data fetch calls when a component mounts
  • It’s safe to use async/await on componentDidMount() hook