Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - wait for promise before render

I can't figure this out, if you can point me to right direction. On my NavMenu.js I have LogIn (two inputs and a submit button, on which I call handleSubmit()

In handleSubmit() I am checking for user login credentials which works great, but after I confirm login, i procede with doing next fetch for checking user roles (and returning promise) and visibility in application

Helper.js

function getAllRoles(formName, sessionVarName) {

    var roleData = [];
    var roleId = sessionStorage.getItem('UserLoggedRoleId');

    fetch('api/User/GetUserRole/', {
    'method': 'POST',
    headers: {
        'Accept': 'application/json, text/plain, */*',
        'Content-Type': 'application/json'
    },
    'body':
        JSON.stringify({
            'roleId': roleId,
            'formName': formName
        })
    }).then(roleResponse => roleResponse.json())
    .then(data => {
        sessionStorage.setItem(sessionVarName, JSON.stringify(data));
        roleData = data;
    });

  var result = Promise.resolve(roleData)

  return result;
}

function checkRoleVisibility(userRoles, role) {} <-- return true or false

export {
  getAllRoles ,
  checkRoleVisibility   
};

In NavMenu.js

import { getAllRoles, checkRoleVisibility } from './common/Helper';

handleSubmit() {

 fetch('api/User/UserAuthentification/', 
 ....then(response => {
       if (response.ok) {
               this.setState(prevState => ({ userLogged: !prevState.userLogged }))

               response.json().then(value => {

               sessionStorage.setItem('UserLogged', true);
               sessionStorage.setItem('UserLabelSession', this.state.userLabel);
               sessionStorage.setItem('UserLoggedRoleId', value.userRole.roleID);

               getAllRoles('NavMenu', 'UserLoggedRoles')
                     .then(roleData => {
                            this.setState({
                                loggedUserRoles: roleData,
                                loading: false
                            });
                        });

               this.props.alert.success("Dobro dosli");
        })
   }
}

But here comes the problem, is that in render() itself, I have a control navBar which calls

checkRoleVisibility(this.state.loggedUserRoles, 'SeeTabRadniNalozi')

from helper, which send this.state.loggedUserRoles as parameter, which suppose to be filled in fetch from handleSubmit() but it's undefined, fetch finish, set loading on true, rendering completes and it doesn't show anything

I have this.state.loading and based on it I show control ...

    let navMenu = this.state.loading ? <div className="loaderPosition">
        <Loader type="Watch" color="#1082F3" height="200" width="200" />
    </div> : !this.state.userLogged ? logInControl : navBar;

If I put a breakpoint on controler I can see my method is being called by getAllRoles function, but also I can see that application keep rendering on going and doesn't wait for promise to return to fill state of loggedUserRoles.

So question is, why rendering doesn't wait for my role fetch nor doesn't wait for promise to resolve before setting loading on true?

I checked this answer Wait for react-promise to resolve before render and I put this.setState({ loading: false }); on componentDidMount() but then loading div is shown full time

like image 363
Veljko89 Avatar asked Jan 12 '19 20:01

Veljko89


1 Answers

From what I see and understand is, that you're calling

this.setState(prevState => ({ userLogged: !prevState.userLogged }))

if the response is okay. This will lead in an asynchronous call from React to set the new State whenever it has time for it.

But you need the resource from

this.setState({
  loggedUserRoles: roleData,
  loading: false
});

whenever you want to set userLogged to true, right?

So it might be that React calls render before you set the loggedUserRoles but after userLogged is set to true. That would result in an error if I understand you correctly.

You could simply set the userLogged state with the others together like this:

this.setState({
  userLogged: true,
  loggedUserRoles: roleData,
  loading: false
});

EDIT:

Moreover you want to change your Promise creation inside of getAllRoles. You're returning

var result = Promise.resolve(roleData)

return result;

This will directly lead to the empty array since this is the initial value of roleData. Therefore you're always returning an empty array from that method and don't bother about the fetched result.

You've multiple ways to solve this. I would prefer the clean and readable way of using async/await:

async getAllRoles(formName, sessionVarName){
  var roleId = sessionStorage.getItem('UserLoggedRoleId');

  const response = await fetch(...);
  const data = response.json();

  sessionStorage.setItem(sessionVarName, JSON.stringify(data));
  return data;

Or if you want to stay with Promise:

function getAllRoles(formName, sessionVarName){
  return new Promise((resolve, reject) => {
    var roleId = sessionStorage.getItem('UserLoggedRoleId');

    fetch(...)
    .then(roleResponse => roleResponse.json())
    .then(data => {
      sessionStorage.setItem(sessionVarName, JSON.stringify(data));
      resolve(data);
    });
like image 56
Björn Böing Avatar answered Oct 23 '22 10:10

Björn Böing