3
\$\begingroup\$

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.

\$\endgroup\$
0

2 Answers 2

1
\$\begingroup\$

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");
\$\endgroup\$
8
  • 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
    Commented 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
    Commented 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
    Commented 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
    Commented 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
    Commented Aug 10, 2016 at 15:01
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.