-
-
Save markerikson/ea4d0a6ce56ee479fe8b356e099f857e to your computer and use it in GitHub Desktop.
// The classic AJAX call - dispatch before the request, and after it comes back | |
function myThunkActionCreator(someValue) { | |
return (dispatch, getState) => { | |
dispatch({type : "REQUEST_STARTED"}); | |
myAjaxLib.post("/someEndpoint", {data : someValue}) | |
.then( | |
response => dispatch({type : "REQUEST_SUCCEEDED", payload : response}), | |
error => dispatch({type : "REQUEST_FAILED", error : error}) | |
); | |
}; | |
} | |
// or, written with async/await syntax: | |
function myThunkActionCreator(someValue) { | |
return async (dispatch, getState) => { | |
dispatch({type : "REQUEST_STARTED"}; | |
let response; | |
try { | |
response = await myAjaxLib.post("/someEndpoint", {data: someValue}); | |
} catch(error) { | |
dispatch({type : "REQUEST_FAILED", error}); | |
return; | |
} | |
dispatch({type: "REQUEST_SUCCEEDED", payload: response}); | |
} | |
} | |
// An example of conditional dispatching based on state | |
const MAX_TODOS = 5; | |
function addTodosIfAllowed(todoText) { | |
return (dispatch, getState) => { | |
const state = getState(); | |
if(state.todos.length < MAX_TODOS) { | |
dispatch({type : "ADD_TODO", text : todoText}); | |
} | |
} | |
} | |
// An example of checking state after a dispatch | |
function checkStateAfterDispatch() { | |
return (dispatch, getState) => { | |
const firstState = getState(); | |
dispatch({type : "FIRST_ACTION"}); | |
const secondState = getState(); | |
if(secondState.someField != firstState.someField) { | |
dispatch({type : "SECOND_ACTION"}); | |
} | |
} | |
} | |
// An example of a thunk dispatching other action creators, | |
// which may or may not be thunks themselves. No async code, just | |
// orchestration of higher-level synchronous logic. | |
function complexSynchronousThunk(someValue) { | |
return (dispatch, getState) => { | |
dispatch(someBasicActionCreator(someValue)); | |
dispatch(someThunkActionCreator()); | |
} | |
} | |
// One solution to the "cross-slice state in reducers" problem: | |
// read the current state in a thunk, and include all the necessary | |
// data in the action | |
function crossSliceActionThunk() { | |
return (dispatch, getState) => { | |
const state = getState(); | |
// Read both slices out of state | |
const {a, b} = state; | |
// Include data from both slices in the action | |
dispatch(type : "ACTION_FOR_A_AND_B", payload : {a, b}); | |
} | |
} |
@JulianKingman: Dan Abramov answered that in his Stack Overflow answers Why do we need middleware for side effects in Redux? and How to dispatch an action with a timeout?.
function myThunkActionCreator(someValue) {
return async (dispatch, getState) => {
dispatch({type : "REQUEST_STARTED"};
let response;
try {
response = myAjaxLib.post("/someEndpoint", {data: someValue});
} catch(error) {
dispatch({type : "REQUEST_FAILED", error});
}
dispatch({type: "REQUEST_SUCCEEDED", payload: response});
}
}
Isn't a mistake in that piece of code? Don't we have to use the await
keyword in front of myAjaxLib.post("/someEndpoint", {data: someValue});
?
@IgorLesnevskiy : good catch! I'd originally written it as promise chaining and converted it, but clearly missed that. Thanks!
Hello. Just for completeness, a question not focused on thunk itself, but on the actual code sample logic:
in async/await example, in catch branch, shouldn't the function return after dispatching error (link)?
Or as an alternative, shouldn't dispatching success be included within try branch?
Thanks for these thunk examples!
@BrainCrumbz: This is actually a tricky topic. The problem is that if an error is thrown while doing the success dispatch, we'd actually end up dispatching the success action as well. That's probably not what you want semantically - the assumption is that the failure action should only be dispatched if the request failed.
Dan Abramov has pointed out similar issues with doing React state updates several times:
@markerikson thanks for replying and for pointing that out, did not consider the implications of the second alternative. So I guess the first alternative, returning after dispatching error, is the only way left:
function myThunkActionCreator(someValue) {
return async (dispatch, getState) => {
dispatch({type : "REQUEST_STARTED"};
let response;
try {
response = await myAjaxLib.post("/someEndpoint", {data: someValue});
} catch(error) {
dispatch({type : "REQUEST_FAILED", error});
// avoid dispatching success, skip the rest
return;
}
dispatch({type: "REQUEST_SUCCEEDED", payload: response});
}
}
Otherwise in case of myAjaxLib.post error, one ends up dispatching error as well as success?
You could use the finally
keyword after catch
for readability.
@markerikson I have a newbie question, that came up as I try to wrap my head around thunk. Why return an action creator that's a function, instead of including the logic in the function itself, and then dispatching conditionally or asynchronously based on the outcome.
Using your first example: