Redux: Reusable actions

2019-07-25 04:30发布

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?

标签: redux
1条回答
Luminary・发光体
2楼-- · 2019-07-25 05:07

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!

查看更多
登录 后发表回答