Let's say I have following action:
export function signIn(data) {
return {
type: USER_SIGN_IN,
promise: api.post('/sign_in', data)
}
}
and the following middleware:
export default function promiseMiddleware() {
return next => action => {
const { promise, type, ...rest } = action
if (!promise) {
return next(action)
}
const SUCCESS = `${type}_SUCCESS`
const REQUEST = `${type}_REQUEST`
const ERROR = `${type}_ERROR`
next({ ...rest, type: REQUEST })
return promise
.then(res => {
next({ ...rest, response: res.data, type: SUCCESS })
return true
})
.catch(error => {
next({ ...rest, response: error.response.data, type: ERROR })
return false
})
}
}
This code is loosely based on https://github.com/reactGo/reactGo/
But what if in then
callback after calling next
I want to make a redirect to another path?
I did following. I passed redirect url through action:
export function signIn(data) {
return {
type: USER_SIGN_IN,
promise: api.post('/sign_in', data),
redirect: '/'
}
}
and added another call of next
method with push
from react-router-redux
:
import { push } from 'react-router-redux'
export default function promiseMiddleware() {
return next => action => {
const { promise, type, redirect, ...rest } = action
if (!promise) {
return next(action)
}
const SUCCESS = `${type}_SUCCESS`
const REQUEST = `${type}_REQUEST`
const ERROR = `${type}_ERROR`
next({ ...rest, type: REQUEST })
return promise
.then(res => {
next({ ...rest, response: res.data, type: SUCCESS })
next(push(redirect))
return true
})
.catch(error => {
next({ ...rest, response: error.response.data, type: ERROR })
return false
})
}
}
It seems like it works, but I'm not sure if this is a good idea or if there are some pitfalls of multiple next
calls and I shouldn't do like this? Or maybe I should use dispatch
method which I can get from promiseMiddleware
params instead of next
for redirecting?
If there are some better approaches for implementing such redirects, please, let me know.
2 Answers 2
Instead of creating your custom promiseMiddleware
you could use the redux-thunk
middleware which accepts a function as an action. And the function will be called with dispatch
and getStore
as parameters.
Imagine you would like to fetch some data.. and you want to do it as an action. like dispatch(getPost(id))
normally your actionCreator getPost
would return the action object,
const getPost = (id) => {
return {
type: "FETCH_POST",
id
}
}
But when you use redux-thunk
you can return a function:
const getPost = (postId) => {
return dispatch => {
// Now you can use dispatch inside this block, even for async calls..
// Start fetching..
dispatch({type: "START_LOADING_POST" }) // imagine this could change your state to {isLoading: true}
http
.get("from.somewhere.net/post/" + postId)
.then((data) => dispatch({type: "POST_LOADED_SUCCESS", postId, data }))
.catch((error) => dispatch(loadingPostFailed({postId, error})))
}
}
I think you know how to apply the middleware to the store, so I skip it. Hope it makes it clear how you can extend your actionCreators
with redux-thunk
to functions and use them specially for async tasks, like promises or even with callback-based libraries.
Redux thunk was part of redux, but its now an own package, but also maintained by Dan Abramov, check out the repo: https://github.com/gaearon/redux-thunk
If you would like to save some time by writing every actionCreator by hand you could create an creator for the actionCreator :D sounds funny, but take a look at this:
const promiseActionCreator = type => {
const
start = type + "_START",
resolved = type + "_RESOLVED",
failed = type + "_FAILED";
return (params) => (dispatch, getState) => {
dispatch({type: start})
http
.get(params.url)
.then(result => dispatch({type: resolved, result }))
.catch(error => dispatch({type: failed, error }))
}
}
// Now you can create/generate your special promise based actionCreator
const getPost = promiseActionCreator("POST_LOADER");
-
1\$\begingroup\$ I got it, thanks, but there will be a lot of boilerplate and repeating code in actionCreators which I'd like to avoid and make my actions small and clean. That's why I were looking for better approach if it exists. \$\endgroup\$ktaras– ktaras2016年08月10日 05:22:44 +00:00Commented Aug 10, 2016 at 5:22
-
\$\begingroup\$ common which code will repeat that much? you have to create the promise, you have to call then / catch anyway.. \$\endgroup\$webdeb– webdeb2016年08月10日 09:52:47 +00:00Commented Aug 10, 2016 at 9:52
-
\$\begingroup\$ This approach handles the promise inside the action, your approach is to create the promise and pass it via the action.., right? \$\endgroup\$webdeb– webdeb2016年08月10日 09:54:10 +00:00Commented Aug 10, 2016 at 9:54
-
\$\begingroup\$ In my approach for each actionCreator I create promise and pass it to the action with some additional params if needed (e.g. redirect path, flash message etc.), and then I don't have to write promise handlers in each actionCreator (since all of them will be pretty similar). Does it make sense? \$\endgroup\$ktaras– ktaras2016年08月10日 11:46:20 +00:00Commented Aug 10, 2016 at 11:46
-
1\$\begingroup\$ OK, but what if I get 404 from API? Should I handle request fail in every actionCreator, check its status and redirect to 404 page? I think such things definitely should be handled somewhere in one single place... \$\endgroup\$ktaras– ktaras2016年08月10日 15:01:57 +00:00Commented Aug 10, 2016 at 15:01
In the case that you want to stop dispatching process and start over with new action (redirecting action), it's ok to use dispatch(action)
instead of next(action)
. Because when you call dispatch(action)
, it will open a new cycle for middlewares.
That means action will be handled from the very first middleware towards that I think it should be. But carefully because when you call dispatch(action)
inside a middleware, that can lead to infinitive loop on dispatch process. I refer you to read this article to understand more about Redux middleware.
Explore related questions
See similar questions with these tags.