-
-
Save jayphelps/fad77fc81cd0220e46ff0ea4b4484e85 to your computer and use it in GitHub Desktop.
Redux-Thunk examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* README FIRST:::::::::::::::::: | |
* | |
* This isn't meant to necessarily be an example of "best practices" | |
* but rather to generally show how you might convert the redux-thunk examples | |
* here: https://gist.github.com/markerikson/ea4d0a6ce56ee479fe8b356e099f857e | |
* | |
* The redux-thunk examples used generic placeholder names, so it's possible | |
* I misunderstood the usecase/intent. | |
* | |
* Examples use ajax utils from rxjs, but you can use promise-based ones if you | |
* _really_ want to. But then they can't be cancelled! Just wrap the promise | |
* in Observable.from(promise) | |
* | |
* All of these use don't really show off the strengths of redux-observable | |
* precisely because the things redux-observable are best at are the things | |
* redux-thunk is awful for (or even impossible without hacks) so people aren't | |
* likely to create examples of them in redux-thunk | |
* | |
* e.g. cancellation (explicit and implicit), debouncing/throttling/etc, | |
* anything dealing with time and/or streaming | |
* | |
* If you're not an RxJS guru, it's perfectly fine to use redux-thunk still | |
* for the simple stuff and defer to redux-observable for the hard stuff. | |
* The docs even suggest this. Do whatever makes you productive :thumbsup: | |
* | |
* People not already in love with Rx will likely see these examples and | |
* think "god that's a lot more boilerplate" and I agree. Again, remember | |
* these are contrived 1:1 examples to the ones presented by @acemarke | |
* | |
* -@jayphelps | |
*/ | |
import { ajax } from 'rxjs/observable/dom/ajax'; | |
// The classic AJAX call - dispatch before the request, and after it comes back | |
const requestEpic = action$ => | |
action$.ofType(REQUEST) | |
.mergeMap(action => | |
ajax.post('/someEndpoint', action.someValue) | |
.map(response => ({ type: 'REQUEST_SUCCEEDED', payload: response })) | |
.catch(error => [{ type: 'REQUEST_FAILED', error }]) | |
.startWith({ type: 'REQUEST_STARTED' }) // this is redundant cause you already dispatched REQUEST | |
); | |
// An example of conditional dispatching based on state | |
const MAX_TODOS = 5; | |
const addTodosIfAllowedEpic = (action$, store) => | |
action$.ofType(ADD_TODO_IF_ALLOWED) | |
.filter(() => store.getState().todos.length < MAX_TODOS) | |
.map(action => ({ type: 'ADD_TODO', text: action.todoText })); | |
// An example of checking state after a dispatch | |
/** | |
* NOT REALLY APPLICABLE? | |
* | |
* I consider this one to be an anti-pattern in redux-thunk as well as in | |
* redux-observable, but if you know of a real world use case that isn't better | |
* suited with a better pattern, please do share and I will remove this comment | |
* -@jayphelps | |
*/ | |
const checkStateAfterDispatchEpic = (action$, store) => | |
action$.ofType(DO_STUFF) | |
.mergeMap(() => new Observable(observer => { | |
const firstState = store.getState(); | |
observer.next({ type: 'FIRST_ACTION' }); | |
const secondState = store.getState(); | |
if (secondState.someField !== firstState.someField) { | |
observer.next({ 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. | |
const complexSynchronousEpic = action$ => | |
action$.ofType(DO_STUFF) | |
.mergeMap(action => Observable.of( | |
someBasicActionCreator(action.someValue), | |
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 | |
/** | |
* I haven't heard of this term before, so I'm not sure of the problem it's | |
* solving. I just copied the apparent behavior. Is this about one reducer | |
* needing the state of another reducer? | |
* -@jayphelps | |
*/ | |
const crossSliceActionThunk = (action$, store) => | |
action$.ofType(DO_STUFF) | |
.map(() => { | |
const { a, b } = store.getState(); | |
return { type: 'ACTION_FOR_A_AND_B', payload: { a, b } }; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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}) | |
.catch(error => dispatch({type : "REQUEST_FAILED", error : error}); | |
}; | |
} | |
// 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}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment