Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - setState in componentWillMount() results in a UI lag

I have to fetch some data in componentWillMount(), this takes some time (2 second), After this, I use setState to Update some values in the state, since this setState re-renders the UI, there is a delay between the component initial render and the render from setState, Is there any way to fix this UI lag?

Update: if I want to use a loading indicator, where should I put it? I use a promise to fetch my data like this:

componentDidMount() {
  api.getData().then((response) => { ... }
like image 862
Adel Avatar asked Dec 21 '25 14:12

Adel


2 Answers

You should never use async operations in componentWillMount or the constructor.
Instead do it in componentDidMount.
You can read about it in the DOCS

setting state synchronously in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method.

Edit
As a followup to your updated question

if I want to use a loading indicator, where should I put it? I use a promise to fetch my data

I've made a small example of fetching data and rendering a loader while data is being fetched.

In this example i'm using a free API tester called jsonplaceholder, I'm using this URL to fetch some random data of users.
You can see that i initialize the state in the contructor with an empty array of users, I'm fetching the users in the componentDidMount life cycle method and updating the users array of the state inside the callback of the promise that has returned. Note that i did that inside a setTimeOut method in order to get a delay of 2 seconds.

Now, React won't really going to wait for our ajax request to get back with the results, it will invoke the render method no matter what, hence doing the ajax request in a life cycle method that runs before the render method (like componentWillMount or the constructor) is not a best practice as mentioned above, so that's why we do it inside the componentDidMount method.

You probably asking, Ok then! But HOW and WHAT should i render before the data is received and then render the data after it is received? I'm glad you asked :)

We can use the life cycle of react to work for us and take advantage of the powerful render options and state updates.
In this example I conditionally rendered a <Loader /> or the data <UserList/> with the help of the ternary operator.

return ({this.state.users.length > 0 ? <UserList /> : <Loader/>);

This way, when ever the users array of state is empty it will render the Loader component, after the state will updated (which will happen after the ajax request is finished) the render method will be invoked again but this time the condition will return true thus the UserList will render and not the Loader.

Here is the full running example:

const apiUrl = "https://jsonplaceholder.typicode.com/users";

const User = ({ name, username, email }) => (
  <div style={{ border: "1px solid #ccc", padding: "15px" }}>
    <div>Name: {name}</div>
    <div>User Name: {username}</div>
    <div>E-Mail: {email}</div>
  </div>
);

const UserList = ({ users }) =>(
  <div>
    {users.map(user => <User key={user.id} {...user} />)}
  </div>
);

const Loader = () => (
<div id="escapingBallG">
	<div id="escapingBall_1" className="escapingBallG"></div>
</div>
);

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      users: []
    };
  }

  componentDidMount() {
    // mimic 2 seconds delay
    setTimeout(() => {
      axios.get(apiUrl)
        .then(users => {
          this.setState({
            users: [...users.data]
          });
        })
        .catch(err => console.log(err));
    }, 2000);
  }
  render() {
    const { users } = this.state;
    return (
      <div>
        {
          users.length > 0 ? <UserList users={users} /> : <Loader />
        }
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
#escapingBallG{
	position:relative;
	width:125px;
	height:43px;
	margin:auto;
}

.escapingBallG{
	background-color:rgb(0,0,0);
	position:absolute;
	top:0;
	left:0;
	width:43px;
	height:43px;
	border-radius:21px;
		-o-border-radius:21px;
		-ms-border-radius:21px;
		-webkit-border-radius:21px;
		-moz-border-radius:21px;
	animation-name:bounce_escapingBallG;
		-o-animation-name:bounce_escapingBallG;
		-ms-animation-name:bounce_escapingBallG;
		-webkit-animation-name:bounce_escapingBallG;
		-moz-animation-name:bounce_escapingBallG;
	animation-duration:1.5s;
		-o-animation-duration:1.5s;
		-ms-animation-duration:1.5s;
		-webkit-animation-duration:1.5s;
		-moz-animation-duration:1.5s;
	animation-iteration-count:infinite;
		-o-animation-iteration-count:infinite;
		-ms-animation-iteration-count:infinite;
		-webkit-animation-iteration-count:infinite;
		-moz-animation-iteration-count:infinite;
	animation-timing-function:linear;
		-o-animation-timing-function:linear;
		-ms-animation-timing-function:linear;
		-webkit-animation-timing-function:linear;
		-moz-animation-timing-function:linear;
	animation-delay:0s;
		-o-animation-delay:0s;
		-ms-animation-delay:0s;
		-webkit-animation-delay:0s;
		-moz-animation-delay:0s;
	transform:scale(0.5, 1);
		-o-transform:scale(0.5, 1);
		-ms-transform:scale(0.5, 1);
		-webkit-transform:scale(0.5, 1);
		-moz-transform:scale(0.5, 1);
}



@keyframes bounce_escapingBallG{
	0%{
		left:0px;
		transform:scale(0.5, 1);
	}

	25%{
		left:41px;
		transform:scale(1, 0.5);
	}

	50%{
		left:103px;
		transform:scale(0.5, 1);
	}

	75%{
		left:41px;
		transform:scale(1, 0.5);
	}

	100%{
		left:0px;
		transform:scale(0.5, 1);
	}
}

@-o-keyframes bounce_escapingBallG{
	0%{
		left:0px;
		-o-transform:scale(0.5, 1);
	}

	25%{
		left:41px;
		-o-transform:scale(1, 0.5);
	}

	50%{
		left:103px;
		-o-transform:scale(0.5, 1);
	}

	75%{
		left:41px;
		-o-transform:scale(1, 0.5);
	}

	100%{
		left:0px;
		-o-transform:scale(0.5, 1);
	}
}

@-ms-keyframes bounce_escapingBallG{
	0%{
		left:0px;
		-ms-transform:scale(0.5, 1);
	}

	25%{
		left:41px;
		-ms-transform:scale(1, 0.5);
	}

	50%{
		left:103px;
		-ms-transform:scale(0.5, 1);
	}

	75%{
		left:41px;
		-ms-transform:scale(1, 0.5);
	}

	100%{
		left:0px;
		-ms-transform:scale(0.5, 1);
	}
}

@-webkit-keyframes bounce_escapingBallG{
	0%{
		left:0px;
		-webkit-transform:scale(0.5, 1);
	}

	25%{
		left:41px;
		-webkit-transform:scale(1, 0.5);
	}

	50%{
		left:103px;
		-webkit-transform:scale(0.5, 1);
	}

	75%{
		left:41px;
		-webkit-transform:scale(1, 0.5);
	}

	100%{
		left:0px;
		-webkit-transform:scale(0.5, 1);
	}
}

@-moz-keyframes bounce_escapingBallG{
	0%{
		left:0px;
		-moz-transform:scale(0.5, 1);
	}

	25%{
		left:41px;
		-moz-transform:scale(1, 0.5);
	}

	50%{
		left:103px;
		-moz-transform:scale(0.5, 1);
	}

	75%{
		left:41px;
		-moz-transform:scale(1, 0.5);
	}

	100%{
		left:0px;
		-moz-transform:scale(0.5, 1);
	}
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
like image 131
Sagiv b.g Avatar answered Dec 24 '25 04:12

Sagiv b.g


Don't know if this will help, you can add loading state till you get all your data and setState. then make the loading sate to false and then render data

class Project extends Component {
constructor(props) {
    super(props);
    this.state = {
        isLoading: true,
        data : data
    };
}


componentDidMount() {
    this
        .props
        .GetData(this.state)
        .then((res) => {
            this.setState.isLoading = false;
        }, (err) => this.setState({errors: err.response}));
}


render() {
     const { isLoading, data} = this.props;

    if(isLoading){
        return (
               <p>loading!!!</p>
        );

    return (
        <p>data</p>
    );
}

}

like image 20
harsha patil Avatar answered Dec 24 '25 02:12

harsha patil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!