Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

redux-thunk: Pause Component Execution Until Action Creator Completes

I've been struggling with this problem for several weeks now. I'm finally throwing in the towel and asking for help on this because I'm clearly not doing something right. I have a React.js app that is using redux and redux-thunk. I'm simply trying to get my Component Container to initiate the loading of data, but not render until the data comes back from the fetch request. Seems simple enough I know. Here is what I've done:

Container Component

'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchActivePlayer } from '../actions/index';
import PlayerDetails from '../components/players/player-detail';
import Spinner from '../components/common/spinner/index';
import store from '../store';

export default class PlayerDetailContainer extends Component {
    constructor(props) {
        super(props);
    }

    componentWillMount() {
        this.props.fetchActivePlayer(this.props.params.player_slug)
    }

    render() {
        if (!this.props.activePlayer.activePlayer) {
            return (
                <Spinner text="Loading..." style="fa fa-spinner fa-spin" />
            );
        }

        return (
            <PlayerDetails 
                player={ this.props.activePlayer.activePlayer } 
            />
        );
    }
}

function mapStateToProps(state) {
    return {
        activePlayer: state.activePlayer
    }
}
export default connect(mapStateToProps, { fetchActivePlayer })(PlayerDetailContainer);

Action Creator

export function fetchActivePlayer(slug) {
    return (dispatch, getState) => {
        return axios.get(`${ROOT_URL}/players/${slug}`)
        .then(response => {
            dispatch({
                type: FETCH_ACTIVE_PLAYER,
                payload: response
            })
        })
        .catch(err => {
            console.error("Failure: ", err);
        });    
    };
}

Store

'use strict';
import React from 'react';
import { browserHistory } from 'react-router';
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
import reducers from './components/reducers/index';

const createStoreWithMiddleware = applyMiddleware(
    thunk,
    promise,
    routerMiddleware(browserHistory)
) (createStore);
export default createStoreWithMiddleware(reducers);

Routes

export default (
<Route path="/" component={ App }>
        <IndexRoute component={ HomePage } />
        <Route path="players/:player_slug" component={ PlayerContainer } />
        <Route path="/:player_slug" component={ PlayerContainer } />
    </Route>
);

Here are the versions I'm using for everything: react = 0.14.7 react-redux = 4.4.1 redux-thunk = 0.5.3

When I run this, I don't receive any errors but it's clear that my action creator is firing but my component container continues instead of waiting for the creator to finish. Like I said, I'm sure I must be missing something really simple but I can't seem to figure out what that is.

Thank you in advance. Any help would be greatly appreciated.

like image 916
Leachy Peachy Avatar asked Mar 11 '23 21:03

Leachy Peachy


1 Answers

  1. your action(fetch) in componentWillMount is async ,component will not wait.
  2. Usually when you fetch some data , you want to know about statuses of fetching process. like "isfetching" in order to showing loader, success and failure in order to showing error.
  3. You can use these statuses to not load/mount/launch component, Until Action Creator Completes.

Thus, you should organize your redux parts somthing like that:

state

activePlayer:{
    data:data,
    isFetching: true/false,
    error:""
    }

action

export const fetchActivePlayer = slug => dispatch =>{
    dispatch({
        type: 'FETCH_ACTIVE_PLAYER_REQUEST',
        isFetching:true,
        error:null
    });

    return axios.get(`${ROOT_URL}/players/${slug}`)
    .then(response => {
        dispatch({
            type: 'FETCH_ACTIVE_PLAYER_SUCCESS',
            isFetching:false,
            payload: response
        });
    })
    .catch(err => {
        dispatch({
            type: 'FETCH_ACTIVE_PLAYER_FAILURE',
            isFetching:false,
            error:err
        });
        console.error("Failure: ", err);
    });

};

reducer

const initialState = {data:null,isFetching: false,error:null};
export const actionPlayer = (state = initialState, action)=>{
    switch (action.type) {
        case 'FETCH_ACTIVE_PLAYER_REQUEST':
        case 'FETCH_ACTIVE_PLAYER_FAILURE':
        return { ...state, isFetching: action.isFetching, error: action.error };

        case 'FETCH_ACTIVE_PLAYER_SUCCESS':
        return { ...state, data: action.payload, isFetching: action.isFetching,
                 error: null };
        default:return state;

    }
};

then your component might look like this (hardcode)

class PlayerDetailContainer extends Component {
    componentWillMount() {
        this.props.fetchActivePlayer(this.props.params.player_slug)
    }
    render() {
        if (this.props.isFetching) {
            return <Spinner text="Loading..." style="fa fa-spinner fa-spin" />

        }else if (this.props.error) {
            return <div>ERROR {this.props.error}</div>
        }else {
            return <PlayerDetails  player={ this.props.data }  />
        }
    }
}
const mapStateToProps = state =>({
        isFetching: state.activePlayer.isFetching,
        data: state.activePlayer.data,
        error: state.activePlayer.error,
})

I don't know how your app looks like. Target of this example is to illustrate approach.

like image 80
Kokovin Vladislav Avatar answered Mar 16 '23 07:03

Kokovin Vladislav