Source:  Twitter logo

How come when calling mapDispatchToProps functions in componentDidMount, mapStateToProps totalDoctorCount: state.doctors.totalDoctorCount wont always load on time and I would get undefined result in console.log("this.props.totalDoctorCount: "+this.props.totalDoctorCount );.
I know it's the nature of async, but is there a way to fix it am i doing something wrong here.

Full Code :

doctorActions

export function getDoctors(filterType){
return function(dispatch){
    axios.get("/api/doctors/"+filterType)
        .then(function(response){
            dispatch({type:"GET_DOCTORS",payload:response.data});
        })
        .catch(function(err){
            dispatch({type:"GET_DOCTORS_REJECTED",payload:err});
        })
}
}

export function getTotalDoctors(){
return function(dispatch){
    axios.get("/api/getTotalDoctors/")
        .then(function(response){
            dispatch({type:"TOTAL_DOCTORS",payload:response.data});
            console.log(response.data);
        })
        .catch(function(err){
            //console.log(err);
            dispatch({type:"TOTAL_DOCTORS_REJECTED",payload:"there was an error rortal doctors"});
        })
}
}

doctorReducer

export function doctorsReducers(state={
doctors:[],
}, action){
switch(action.type){
    case "GET_DOCTORS":
    // return the state and copy of boos array from state
        return {...state,doctors:[...action.payload]}
    break;

    case "TOTAL_DOCTORS":
    // return the state and copy of boos array from state
        return {
            ...state,
            totalDoctorCount:action.payload
        }
    break;

}
return state;
}

server API

app.get('/doctors/:filterType',function(req,res){
let filterType = req.params.filterType;
var query = {};
if(filterType == "dateCreated"){
    query = {date_created: 'desc'};
}else if(filterType == "dateUpdated"){
    query = {date_updated: 'desc'};
}
Doctors.find({}).sort(query).limit(3).exec(function(err,doctors){
    if(err){
        throw err;
    }
    res.json(doctors);
});
});

app.get('/getTotalDoctors',function(req,res){
Doctors.count({}, function(err, count){
    if(err){
        throw err;
    }
    res.json(count);
});
});

component

class MainAdmin extends React.Component{

    constructor(){
        super();
        this.state = {
            selected_filter:"dateCreated"
        };
    }

    openAddDoctorModal = () => {
        this.setState({AddDoctorModal:true});
    }

    closeAddDoctorModal = () => {
        this.setState({AddDoctorModal:false});
    }


    componentDidMount(){
        this.props.getTotalDoctors();
        this.props.getDoctors(this.state.selected_filter);
    }



    loadPage = (pageNum) => {
        //alert(pageNum);
        this.props.loadPage(pageNum,this.state.selected_filter);
    }


    render(){

        const doctorsList = this.props.doctors.map(function(doctorsArr){
            return(
                <Col xs={12} sm={12} md={12} key={doctorsArr._id}>
                    <DoctorsItem
                        _id = {doctorsArr._id}
                        doc_fname = {doctorsArr.doc_fname}
                        doc_lname = {doctorsArr.doc_lname}
                    />
                </Col>
            )
        });

        //const lengthPage = parseInt(this.props.totalDoctorCount/3);
        console.log("this.props.totalDoctorCount2: "+this.props.totalDoctorCount );
        const pages = parseInt(this.props.totalDoctorCount/3, 10);
        console.log("pages: "+pages );

        const pageNums = [...Array(pages)].map((pageNum, i) => {
            return(
                <Col xs={2} sm={2} md={2} key={i+1}>
                    <Button onClick={() => this.loadPage(i+1)} bsStyle="success" bsSize="small">
                        {i+1}
                    </Button>
                </Col>
            )
        });

        return(
            <Well>
                <Row style={{marginTop:'15px'}}>
                    {doctorsList}
                </Row>
                <Row style={{marginTop:'15px'}}>
                    {pageNums}
                </Row>
            </Well>
        )



    }
}

function mapStateToProps(state){
    return{
        doctors: state.doctors.doctors,
        totalDoctorCount:state.doctors.totalDoctorCount
    }
}


function mapDispatchToProps(dispatch){
    return bindActionCreators({
        getDoctors:getDoctors,
        loadPage:loadPage,
        getTotalDoctors:getTotalDoctors
    },dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(MainAdmin);

There are several ways you can handle this, but you must first understand how to handle asynchronous actions that affect your dom.

Whenever a component mounts (and depending on how you've set your app up, whenever a change is made to props, state, etc), its render function is called. In your example, the component mounts, then asks the server for the list of doctors, calls render(), and then receives the list of doctors from the server. To rephrase, by the time it called the render method, it has not yet received the list of doctors from the axios call.

Apologies if you understood all of this. Now for why this.props.totalDoctorCount is returning undefined: your app's state.totalDoctorCount is not being defined until the function getTotalDoctors resolves (i.e., hears back from server). You can fix this rather simply by defining totalDoctorCount as 0 in your defaultState (where you defined doctors as an empty array).

On the other hand, do you really want users to see/think that there are a total of 0 doctors until the server responds in time? This might be a good opportunity to consider a loading component. What I like to do is right below render(), check for the existence of whatever list you need to iterate through and if it is empty, you can return a LoadingComponent (you can make this on your own and use it wherever something needs to load).

This by itself is not enough, because you don't want the page to load indefinitely in the event that you actually don't have any doctors, so this LoadingComponent should only appear if the function that is retrieving the list that it's concerned with is still 'fetching'. So perhaps you can implement three actions that are called before you fetch, after the fetch is responded to, and if there is an error.

So to outline:

1) MainAdmin mounts.

2) GetDoctors and GetTotalDoctors are called.

3) A new action isFetching is called, leaving your state as:

{
    doctors: [],
    totalDoctors: 0, //assuming you have added this to defaultState
    isFetchingDoctors: true
}

4) MainAdmin calls render().

5) since state.doctors is empty and state.isFetchingDoctors is true, MainAdmin.render() returns your new LoadingComponent.

6) Your server responds to your axios call with the list of doctors and the totalDoctorCount (note: this will happen at different times, but for sake of simplicity, I am treating them as happening together).

7) Your success handler updates your state with the new list of doctors:

{
    doctors: [1, 2, 3],
    totalDoctors: 3,
    isFetchingDoctors: true
}

8) MainAdmin calls render() again because of the change in state, but because state.isFetchingDoctors is still true, it will still show LoadingComponent.

8) Your second new action isFetched() is called, leaving your state as:

{
    doctors: [1, 2, 3],
    totalDoctors: 3,
    isFetchingDoctors: false
}

9) MainAdmin calls render() again, but this time the conditions to indicate that it is no longer loading are met, and you can safely iterate through your list of doctors.

One final note: You could also set isFetching to false in your reducer as soon as you GetDoctors but I personally like to separate the asynchronous status functions into their own function to keep to the motto of every function only tasked with doing one thing.

2 users liked answer #0dislike answer #02
Robert Taussig profile pic
Robert Taussig

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).