4

Say I have the following epic:

const getPostsEpic = (action$, store) => {
    return action$.ofType(actionTypes.REQUEST_POSTS)
        .switchMap(action =>
            ajax.getJSON(`api/posts?key=${action.key}`)
            .map(response =>
                receivePosts({type: RECEIVE_POSTS, posts: response})
            ).takeUntil(
                action$.ofType(actionTypes.ABORT_GET_POSTS)
            )
};

and say my reducer is something like

function reducer(
  state = {
    isFetching: false,
    didInvalidate: true,
    items: []
  },
  action
) {
  switch (action.type) {
    case INVALIDATE_POSTS:
      return Object.assign({}, state, {
        didInvalidate: true
      })
    case REQUEST_POSTS:
      return Object.assign({}, state, {
        isFetching: true,
        didInvalidate: false
      })
    case RECEIVE_POSTS:
      return Object.assign({}, state, {
        isFetching: false,
        didInvalidate: false,
        items: action.posts,
      })
    default:
      return state
  }
}

I want to make sure that posts are only fetched if my state's didInvalidate === true, is there a good way to make this work with my epic? Could do something like this, but it's not that pretty IMO:

const getPostsEpic = (action$, store) => {
    return action$.ofType(actionTypes.REQUEST_POSTS)
        .switchMap(action => {
            const state = store.getState();
            if (state.didInvalidate) {
                return ajax.getJSON(`api/posts?key=${action.key}`)
                    .map(response =>
                        receivePosts({type: RECEIVE_POSTS, posts: response})
                    ).takeUntil(
                        action$.ofType(actionTypes.ABORT_GET_POSTS)
                )
            else {
                return Observable.of({type: RECEIVE_POSTS, posts: state.items});
            }
        }
};

Btw, I'm using this with React. I'm sure this is a pretty common problem, so maybe there's a better way of handling this outside my epics?

simen-andresen
  • 2,217
  • 4
  • 25
  • 39
  • Maybe I'm misunderstanding something, but why dispatch REQUEST_POSTS at all if didInvalidate is false? – msolvaag Nov 07 '17 at 22:21
  • @msolvaag, the reason is that I have different react routes that depend on the posts data, and I typically dispatch `REQUEST_POSTS` from the different component's `componentDidMount`. When loading a component, the posts may or may not have been loaded already. I could of course check the `isInvalidated` flags all around the place, but that also seems sub-optimal. – simen-andresen Nov 08 '17 at 09:24

1 Answers1

0

You can use if for branching, like this:

const mockAjax = () => Promise.resolve({posts: [4, 5, 6, 7]});

const fetchPost = (action$) => Rx.Observable.fromPromise(mockAjax())
  .map(({posts}) => ({type: RECEIVE_POSTS, posts}))
  .takeUntil(action$.ofType(ABORT_GET_POSTS))

const defaultPosts = (action$, store) => Rx.Observable.of({type: RECEIVE_POSTS, posts: store.getState().items});

const getPostsEpic = (action$, store) =>
  action$.ofType(USER_REQUEST)
    .mergeMap(() => Rx.Observable.if(
      () => store.getState().didInvalidate, // condition
      fetchPost(action$), // if true
      defaultPosts(action$, store) // if false
    )
    .do(x => console.log(x))
)

Check the demo in her: http://jsbin.com/jodaqopozo/edit?js,console,output

Clicking valid/invalid button and then click 'Post Request' will log different value.

Hope this helps.

Tom Marulak
  • 633
  • 4
  • 10