cyclejs-community / redux-cycles

Bring functional reactive programming to Redux using Cycle.js
MIT License
745 stars 30 forks source link

Action from an http requetis fired twice #35

Closed dbertella closed 7 years ago

dbertella commented 7 years ago

I have a component that at componentDidMount will fire an action that makes an http request. What I want is to set an isFetching flag in the reducer. Everything works as expected the first time I load the component, but the second the flag isFetching is never set to true. I played just a little with the middleware during this weekend so I'm not really sure why I have this problem.

This is my main, I just readapted the code from one of the example.

const fetchPostById = (sources) => {
  const post$ = sources.ACTION
    .filter(action => action.type === ActionTypes.POST_REQUESTED)
    .map(action => action.postId);

  const request$ = post$
    .map(postId => ({
      url: `${BASE_URL}posts/${postId}`,
      category: 'post'
    }));

  const response$ = sources.HTTP
    .select('post')
    .flatten();

  const action$ = xs.combine(post$, response$)
    .map(arr => actions.receivePostById(arr[0], arr[1].body));

  return {
    ACTION: action$,
    HTTP: request$
  }
}

And this is the reducer:

const post = (state = initialPost, action) => {
  switch (action.type) {
    case 'POST_REQUESTED':
      return {
        ...state,
        isFetching: true,
      };
    case 'POST_RECEIVED':
      return {
        ...state,
        isFetching: false,
        [action.postId]: action.post,
      };
    default:
      return state;
  }
};

If I log what's going on with my request, I can see that isFetching is false at the beginning, then the action POST_REQUESTED is fired and isFetching is set to true. When POST_RECEIVED is being fired (from the cycle code) isFetching goes back to false. That's the normal case. On the next time POST_RECEIVED is being fired two times for the same action and isFetching flag is never set to true.

screen shot 2017-02-27 at 18 19 34

You can see an online version here https://dbertella.github.io/food-and-quote/ in case. Am I missing something?

lmatteis commented 7 years ago

The problem is with the use of combine: https://github.com/staltz/xstream#-combinestream1-stream2

The second time you call the POST_REQUESTED action from react, it's gonna emit POST_RECEIVED even without a response (because combine uses the latest response).

I think you may need sampleCombine instead (https://github.com/staltz/xstream/blob/master/EXTRA_DOCS.md#-samplecombinestreams) But make sure the source stream is the response.

Hope it makes sense. Let me know if you can figure it out.

dbertella commented 7 years ago

Thank you very much! I'll look into it and let you know! Edit I couldn't find the right way to make sampleCombine working instead, but I found out that in my case I don't need the combine at all, at least not for the single post.

const action$ = sources.HTTP
    .select('post')
    .flatten()
    .map(res => actions.receivePostById(res.body.id, res.body));

That makes the trick, but I will have to understand better cycle for more advanced stuff anyway. Thank you again!

lmatteis commented 7 years ago

Yeah if you don't need the post info in the stream, then you don't need combine 😃

Anyway in case you need the post this should work:

import sampleCombine from 'xstream/extra/sampleCombine'

const action$ = response$
  .compose(sampleCombine(post$))
  .map(([ response, post ]) => actions.receivePostById(post, response.body))