Skip to content

Instantly share code, notes, and snippets.

@jayphelps
Forked from markerikson/redux-thunk-examples.js
Last active August 31, 2018 12:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jayphelps/fad77fc81cd0220e46ff0ea4b4484e85 to your computer and use it in GitHub Desktop.
Save jayphelps/fad77fc81cd0220e46ff0ea4b4484e85 to your computer and use it in GitHub Desktop.
Redux-Thunk examples
/**
* 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 } };
});
// 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