Chen Jian's Java Blog 

Ajax in Redux with Thunk - Can't be more weird

by Chen Jian


Posted on 2018-06-22 21:38 in Frontend


How can a ajax call fit into Redux paradigm?

  • State: The state will be updated after ajax call is over. No problem.
  • Reducer: Reducer will change the state when ajax call is over. No problem.
  • Action: Action can be used to define "Ajax call over"

But where to put the logic of really doing an ajax call?

None of the 3. It should be a plain function, which will dispatch "Ajax call over" action when Ajax fetching is done. But since people are so obesessed with Redux's paradigm, this ajax calling logic is defined as a SPECIAL ACTION

  • It normally doesn't have an action type, and it is not handled by reducers
  • But it can be "dispatched"
  • It's not a data structure any more, but a function itself.

But how can a action be a function? That's where the middleware "Thunk" comes into, which allows you to write action creators that return a function instead of an action


Here is an example. Before you go into it, you must know normally

  • Ajax call success and Ajax call failure will be defined as different actions
  • "Ajax call started" is also an action, since it will change the state in terms of "showing loading spinner"
//register the middleware
const rootStore = createStore(reducer, 
		applyMiddleware(thunkMiddleware) //allows you to write action creators that return a function instead of an action 
);


//"real" actions and their reducers 
function counterReducer(state={value:0}, action){
    switch(action.type){
    	case 'getCounterStart': 
    		return {
    			...state, 
    			isFetching: true,  //used to indicate that the ajax request is running
    			error: undefined   //used to clear existing error message 
    		}
    	case 'getCounterDone':
    		return {
				...state, 
				isFetching: false, //used to indicate that the ajax request is running
				error: undefined,  //used to clear existing error message
				value: action.counter.theValue
			}    		
    	case 'getCounterFailed':
    		return {
				...state, 
				isFetching: false,  //used to indicate that the ajax request is running
				error: action.error //used to show the error message
			}    		    		
		...
    }
}


//the ajax calling as an "action"
function getCounterAction(){
	const actionAsFunction =  function(dispatch){
		
		dispatch({type:'getCounterStart'}); //dispatch an "loading started" action first
		
		return axios.get("http://localhost:12180/fo/rest/counter/get")
				.then(function(response){
					console.log(response.data); //success
					dispatch({type:'getCounterDone', counter: response.data});
				})
				.catch(function(error){
					if(error.response){ //failure with http response, such as a http 500 response
						dispatch({type:'getCounterFailed', error: error.response.error_description_for_user})
					}else{ //failure without http response
						dispatch({type:'getCounterFailed', error: "Unknown Error"})
					}
				});				
	}
	
	return actionAsFunction;  //so the action itself is a function
}




 




//the component   
class CounterComponent extends Component {
    constructor(props){
        super(props);
    }

    render(){
    	const errorStyle = {color:'red'};
    	
        return (

            <div>
                <h2>Counter</h2>                
                
                {/* Loading indicator */}
                {this.props.counter.isFetching &&  
                	<div>Loading...</div>
                }
                
                {/* Show error */}                 
                {this.props.counter.error && 
                	<div style={errorStyle}>{this.props.counter.error}</div>
                }
                
                {/* Show values */}                
                <h3>{this.props.counter.value}</h3>                    
                <div>
		            <button onClick={ e => this.props.onChange(1)}>+</button>   
		            <button onClick={ e => this.props.onChange(-1)}>-</button>    
                </div>
                                               
            </div>
        );
    }

}



class CounterComponentContainer extends Component {
	componentDidMount(){
		this.props.dispatch(getCounterAction()); //fetch the value when component is first rendered
	}
	
	render(){
		return (
				<CounterComponent counter={this.props.counter} onChange={this.props.onChange}/>
		);
	}
}

CounterComponentContainer = connect(
    mapStateToProps,
    mapStateToDispatch
) (CounterComponentContainer);  



export default CounterComponentContainer;



No one has commented yet.
该日志评论功能被禁用了。