bananaoomarang / isomorphic-redux

Isomorphic Redux demo, with routing and async actions
https://medium.com/@bananaoomarang/handcrafting-an-isomorphic-redux-application-with-love-40ada4468af4
MIT License
456 stars 87 forks source link

How to chain action Promise #66

Open govind999 opened 7 years ago

govind999 commented 7 years ago

Hi Team,

I am following your code boilerplate for my project, I ran into a situation where i am making an API call which responds with xml data, so i need to parse that data and then send to reducer. May i know how to do that.

// first the api call has to be made,
export function getTodos() {
  return {
    type:    'GET_TODOS',
    promise: request.get(API_URL)
  }
}
then it need to parse the response

// using https://github.com/Leonidas-from-XIV/node-xml2js
 parseString(res, function (err, result) {
  // data gets parsed here
});

Then it has to send the raise the event. I tried as below but it is throwing error

const request = axios.get(url).then(res=>{
        parseString(res, function (err, result) {
           if(result){
                dispatch({
                   type: GET_TODOS,
                   data:result
                })
            }
            if(err) throw err
        });
    }).catch(err=>console.error(error))
};

I am getting below error

Error: Actions must be plain objects. Use custom middleware for async actions.

Thanks.

bananaoomarang commented 7 years ago

If you don't have control over the parseString function and don't want to introduce a Promise 'polyfill' library and use a promisify function, you could do something like:

const request = axios.get(url).then(res => {

    return (new Promise((resolve, reject) => {

        parseString(res, function (err, result) {
            if(err) return reject(err)

            return resolve(result)
        })

    })

})

I'll admit I'm unclear on the code, exactly. But you might want to do something like:

dispatch(createMyFirstAsyncPromiseAction())
    .then((success) => {
        if(success) return dispatch(createMySecondAsyncPromiseAction())
    })

Though I would probably discourage this coupling/chaining of actions and consider consolidating into one action. If you really want this complication redux-saga is pretty great.

That error means you're triggering a dispatch of a non-object somewhere, likely in a middleware.

Redux thunk also exists, and there's no reason you can't use it alongside the Promise middleware included here, or switch to i entirely. IMO it can get a little messy/hard to read, but it is very flexible.

govind999 commented 7 years ago

Thanks for the answer, yeah it is asyn operation, that is the reason. Do you suggest changing from promiseMiddleware to redux saga or thunk? is it easy to change that in your code? do you suggest that way, because i am using your code as boilerplate and project is up and running.

bananaoomarang commented 7 years ago

I personally prefer writing promise middleware to thunk, and as long as you understand the code you can really extend it to do whatever you want. If you look at saga or thunk and like either you can start using them.

For this case though any of my above solutions should work fine, you can chain async actions with the promise middleware as in the last example. I would just say that if you're chaining a bunch of actions you should think about refactoring them, because coupling actions isn't a really a desirable pattern.

That said I would always advocate your better judgment! Don't worry so much about the implementation, worry that you understand it, are comfortable working with and extending it etc.

To be clear, the first example above is to combine these things into one action (ie, if they will always be run in sequence) and to adapt the callback code to a promise. The second example is how to do it without much refactoring, just dispatch and use use then to dispatch another action after.

govind999 commented 7 years ago

Thanks @bananaoomarang , i liked both your solutions, let me try now.

govind999 commented 7 years ago

i will change the code like this, do you agree

export function getSearcData() {

  const request = axios.get(API_URL).then(res => {

    return (new Promise((resolve, reject) => {

        parseString(res, function (err, result) {
            if(err) return reject(err)
            return resolve(result)
        })

    })
  })     

  return {
    type:    'GET_DATA_XML_API',
    promise: request
  }
bananaoomarang commented 7 years ago

That looks good to me

govind999 commented 7 years ago

Works like a charm, thank you. I know this is more like issues forum, if i want to ask you questions in future, what is best place to reach you?

bananaoomarang commented 7 years ago

This works fine for me (though I can’t promise quick replies)

I think my email is on my Github profile page too? If not you can just use here.

On Fri, 30 Sep 2016, 15:34 govind999, notifications@github.com wrote:

Works like a charm, thank you. I know this is more like issues forum, if i want to ask you questions in future, what is best place to reach you?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/bananaoomarang/isomorphic-redux/issues/66#issuecomment-250833265, or mute the thread https://github.com/notifications/unsubscribe-auth/AAxy12xf0GBd7MRpnoBXm_NujJwXInV2ks5qvWRKgaJpZM4KKzka .

govind999 commented 7 years ago

Just one more question, as it is rawHTML, i am planning to get it from store.getState().RawHTMLdata and pass to template of HTML. Because the HTML is in state its messing up UI. How can use and then delete it from store before i send it to UI? any ideas?

bananaoomarang commented 7 years ago

Not sure what you mean by this.

Maybe you want: https://facebook.github.io/react/tips/dangerously-set-inner-html.html

govind999 commented 7 years ago

What i mean to say is when the reducer has HTML data, it will go to store and then go UI as initial state right? so its rawHTML and is messing up UI. I thought i will grab the HTML from store and then send to template and delete it from store before sending to store, i posted on stack overflow too..

http://stackoverflow.com/questions/39799728/delete-one-reducer-data-from-store-before-rendering

bananaoomarang commented 7 years ago

Hmmm, I wouldn't necessarily advise putting HTML in the store but AFAIK no reason you shouldn't be able to. The initialState passed from the backend is just a serialized, JSON string so you should be have runs of HTML in there.

Are you seeing errors in the console or are you just unable to display it? If it's the latter you probably just have to signal you want it to be displayed as HTML and not text.

As I say, no technical reason you can't do this, but you might want to consider if it's the best approach.

govind999 commented 7 years ago

If i keep the HTML in the store, the initial state which goes to UI has this data. So the html is getting printed on the page from store object as they are HTML tags, So want i did is, i manually removed that html from initial state.

  const initialState = store.getState();

const HTMLstr = '<!doctype html>' + renderToString(); return HTMLstr;

       <script dangerouslySetInnerHTML={{__html: `window.__CONFIG__ =${JSON.stringify(config)};`}} charSet="UTF-8"/>