1

I have an action, that uses a redux thunk, that looks like so:

export function fetchData(query) {
  return dispatch => {
    return fetch(`http://myapi?query=${query}` ,{mode: 'cors'})
      .then(response => response.json())
      .then(json => { dispatch(someOtherAction(json)) })
    }
  }
}

and then my someOtherAction actually updates state:

export function someOtherAction(data) {
    return {
        action: types.SOME_ACTION,
        data
    }
}

But i want it to be possible for the fetchData action creator to be reusable so that different parts of my app can fetch data from myapi and then have different parts of the state based on that.

I'm wondering what is the best way to reuse this action? Is it acceptable to pass a second parameter in to my fetchData action creator that stipulates which action is called on a successful fetch:

export function fetchData(query, nextAction) {
  return dispatch => {
    return fetch(`http://myapi?query=${query}` ,{mode: 'cors'})
      .then(response => response.json())
      .then(json => { dispatch(nextAction(json)) })
    }
  }
}

Or is there an accepted way of doing this sort of thing?

Mike Rifgin
  • 10,409
  • 21
  • 75
  • 111
  • Looks like you can't pass additional args to actions. – Mike Rifgin Oct 10 '16 at 19:10
  • I have something very similar hacked together. I also do a ``nextErrorAction`` as an additional argument to ``fetchData``. Further, I also return a promise and ``dispatch(nextAction(data))`` along with ``resolve`` when the status code is 2xx and ``dispatch(nextErrorAction(data))`` & ``reject`` for 4xx/5xx. It ends up working quite well and is just a few lines of code. – iamnat Oct 10 '16 at 19:16
  • ah ok, that's interesting. Can't find any documentation as to whether you should or should not pass extra params or not. Thinking I might not need to fetch in the action at all? https://github.com/gaearon/redux-thunk defines a `fetchSecretSauce` method that does the fetch outside of the action creator. – Mike Rifgin Oct 10 '16 at 19:20

1 Answers1

1

I use a middleware for that. I have defined the fetch call in there, then in my actions I send the URL to fetch and the actions to dispatch when completed. This would be a typical fetch action:

const POSTS_LOAD = 'myapp/POST_L';
const POST_SUCCESS = 'myapp/POST_S';
const POST_FAIL = 'myapp/POST_F';

export function fetchLatestPosts(page) {
  return {
    actions: [POSTS_LOAD, POST_SUCCESS, POST_FAIL],
    promise: {
      url: '/some/path/to/posts',
      params: { ... },
      headers: { ... },
    },
  };
}

When calling that action, the POST_LOAD action will be dispatch automatically by the middleware just before the fetch request it's executed. If everything goes well the POST_SUCCESS action will be dispatched with the json response, if something goes wrong the POST_FAIL action will be dispatched by the middleware.

All the magic it's in the middleware! And it's something similar to this:

export default function fetchMiddleware() {
  return ({ dispatch, getState }) => {
    return next => action => {
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }

      const { promise, actions, ...rest } = action;

      if (!promise) {
        return next(action);
      }

      const [REQUEST, SUCCESS, FAILURE] = actions;
      next({ ...rest, type: REQUEST }); // <-- dispatch the LOAD action


      const actionPromise = fetch(promise.url, promise); // <-- Make sure to add the domain

      actionPromise
        .then(response => response.json())
        .then(json => next({ ...rest, json, type: SUCCESS })) // <-- Dispatch the success action
        .catch(error => next({ ...rest, error, type: FAILURE })); // <-- Dispatch the failure action

      return actionPromise;
    };
  };
}

This way I have all my requests on a single place and I can define the actions to run after the request it's completed.

------------EDIT----------------

In order to get the data on the reducer, you need to use the action name you defined on the original action creator. The following example shows how to handle the POST_SUCCESS action from the middleware to get the posts data from the json response.

export function reducer(state = {}, action) {
  switch(action.type) {
    case POST_SUCCESS:  // <-- Action name
      return {
        ...state,
        posts: action.json.posts, // <-- Getting the data from the action
      }
    default:
      return state;
  }
}

I hope this helps!

Crysfel
  • 7,926
  • 3
  • 33
  • 41
  • Thanks for the answer @crysfel. One question. What are POST_LOAD, POST_SUCCESS? Is it an action creator function or is it an object with a type and an action. Any chance u could add an example of what the actions are there ? – Mike Rifgin Oct 14 '16 at 06:07
  • 1
    It's just the action name: `const POSTS_LOAD = 'myapp/POST_L';` You are going to receive this on the reducer. Based on this name you will do whatever you want with the data or your state. I've added an example of the reducer. – Crysfel Oct 14 '16 at 08:09
  • Apologies, Ignore previous. I had a silly mistake in my code. Thanks so much for your assistance. I learnt a lot here and now also feel up to speed with creating my own redux middleware. – Mike Rifgin Oct 14 '16 at 16:01