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;