DEV Community

Jimmy Hung
Jimmy Hung

Posted on • Edited on

React fetching data before rendering in 2020

TLDR:
Do not fetch data in ComponentWillMount, do it in ComponentDidMount

There are few use cases in React project where you think you need to fetch data before rendering.

When you quickly google 'fetching data before first rendering in React', this is the first answer that popped up from StackOverlflow.

The most voted answer which suggests to do in componentWillMount(), a method fired off before render() in React lifecycle will be completely deprecated in React v17 (as of current writing on 5/10/2020 React is at version 16.13.1).

The mechanism of data lifecycle in React is complex enough that Facebook created Relay and Suspence in React 16.6 to address it. Read the second article to fully understand all use cases.

Here I present some issues that one think they need to fetch data before rendering, but it's not always the case.

It is best to keep your data fetching in componentDidMount()
(it's okay to have first renders with no data)

Issues:

  1. The render gets compile error when data is not found
  2. A child component render relies on data response from parent component
  3. A child component that has heavy synchronous code relies on data response from parent component

Cases such as 1 and 2. You can implement an if statement to conditionally render based on if your data returned or not.


  if( dataIsReturned === true) {
    <RenderYourData/> 
  } else {
   <h1> Loading </h1>
  }


Enter fullscreen mode Exit fullscreen mode

or you can simplify by using a ternary statement:

 {
  dataIsReturned ? <RenderYourData/> : <h1> Loading </h1>
 }

Enter fullscreen mode Exit fullscreen mode

In case #3 usually comes from a design fault to start with. But because it is legacy code, take too much resource to refactor, etc. It Usually the problem is presented this way.


   //start of LegacyChild.js

   let initiator = init(data);

   // 5000 lines of code

   function LegacyChild () = {
     return initiator;
   }

   export LegacyChild;
   //end of LegacyChild.js




   //start of Parent.js

   import <LegacyChild/> from './LegacyChild';

   class Parent extends React.Component {

     componentDidMount(){
      fetch()
       .then(this.setState(data));
     }

     render() {
        <LagacyChild/>
     }
   }

   React.render(<Parent/>, document.getElementById('root');
   //end of Parent.js
Enter fullscreen mode Exit fullscreen mode

Notice two things:

  1. LegacyChild.js contains code outside of exported function, in this case there are 5000 lines of synchronous javascript code that is impossible to refactor and they depend on an external data source outside of legacyChild.js. The function LegacyChild's definition depends upon those 5000 lines of code, by leveraging the power of closure.

  2. In Parent.js, LegacyChild.js is imported at the top of Parent.js, it will result in a compiled error because it reads LegacyChild.js which depends on the data response in Parent component.

In this case you can delay the import of LegacyChild.js by using dynamic import.

 class Parent extends React.Component {

     constructor() {

      this.state = {
        dataIsReturned : false ;
      }
      this.LegacyChild = null;
     } 

     componentDidMount(){
      fetch()
       .then(async (data) => {
          let obj = await import('./legacyChild');
          this.LegacyChild = obj.default;
          this.setState({dataIsReturn : true});
       ).catch( err => console.error(err);)
     }

     render() {
        if dataIsReturned ? <this.LegacyChild/> : <h1> Loading </h1>
     }
   }
Enter fullscreen mode Exit fullscreen mode

Here we only changed the order of importing legacyChild.js, we still got to fetch the data inside componentDidMount().

Let me know other use case in the comment that I didn't mentioned where you think you are forced to fetch data before rendering.

Top comments (3)

Collapse
 
raddevus profile image
raddevus • Edited

Your article really helped me to get through this issue and i really appreciate you writing it up.
FYI, there are a couple of issues in your code example in the render() method.
It needs a couple of changes and the final render() method should look like the following:

render() {
        this.state.dataIsReturned ? return (<this.LegacyChild/>) : return (<h1> Loading </h1>)
     }
Enter fullscreen mode Exit fullscreen mode

Here are the issues:

  1. have to refer to state variable with this.state.<varname>
  2. The ternary isn't really an if statement, remove the if
  3. the render() method won't automatically return the appropriate value on the right, just need to wrap both in a explicit return() and all will be good.

Again, thanks for writing this up. It really helped me out a ton. This discussion isn't found anywhere else really (couldn't even find a good StackOverflow) so this is a very useful article.

Collapse
 
a_m_h_gad profile image
Jad

Thank you very much, It's very helpful.
I'm currently suffering from the second case, where a child element at app.js file renders before data is fetched, I'm gonna try this out tomorrow and see where it will go.
thanks again.

Collapse
 
greenon profile image
Pramod Vadrevu

Hi, Thanks for this article. I think there is a typo in the above code. It should be dataIsReturned and not dataIsReturn in the componentDidMount() block.