简体   繁体   English

使用thunk中间件从非反应组件调度异步redux操作

[英]Dispatch async redux action from non-react component with thunk middleware

I am building an react / redux webapp where I am using a service to make all my API calls. 我正在构建一个react / redux webapp,我正在使用一个服务来进行所有的API调用。 Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store. 每当API返回401 - Unauthorized我想将注销操作发送到我的redux商店。

The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions . 现在问题是我的api-service没有反应组件,所以我无法获得对dispatchactions的引用。 What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? 我首先做的是导出存储并手动调用dispatch ,但正如我在这里阅读的那样如何使用超时调度Redux操作? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user. 这似乎是一个不好的做法,因为它要求商店是单身,这使得测试很难并且在服务器上呈现是不可能的,因为我们需要为每个用户提供不同的商店。

I am already using react-thunk ( https://github.com/gaearon/redux-thunk ) but I don t see how I can inject dispatch` into non-react components. 我已经在使用react-thunkhttps://github.com/gaearon/redux-thunk ),但我不t see how I can inject dispatch注入非反应组件。

What do I need to do? 我需要做什么? Or is it generally a bad practice to dispatch actions outside from react components? 或者从反应组件外部调度操作通常是一种不好的做法? This is what my api.services.ts looks like right now: 这就是我的api.services.ts现在的样子:

... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';

export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
  let promise = new Promise((resolve, reject) => {
    const headers = {
      "Content-Type": "application/json",
      "Authorization": getFromStorage('auth_token')
    };
    const options = {
      body: data ? JSON.stringify(data) : null,
      method,
      headers
    };
    fetch(url, options).then((response) => {
      const statusAsString = response.status.toString();
      if (statusAsString.substr(0, 1) !== '2') {
        if (statusAsString === '401') {
          //  !!!!!-> here I need to dispatch the logout action
          store.dispatch(UserActions.logout());
        }
        reject();
      } else {
        saveToStorage('auth_token', response.headers.get('X-TOKEN'));
        resolve({
          data: response.body,
          headers: response.headers
        });
      }
    })
  });
  return promise;
};

Thanks! 谢谢!

If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument: 如果您正在使用redux-thunk,则可以从动作创建者返回一个函数,该函数具有dispatch has参数:

const doSomeStuff = dispatch => {
  fetch(…)
   .then(res => res.json())
   .then(json => dispatch({
     type: 'dostuffsuccess',
     payload: { json }
    }))
    .catch(err => dispatch({
      type: 'dostufferr',
      payload: { err }
     }))
}

Another option is to use middleware for remote stuff. 另一个选择是使用中间件来进行远程操作。 This works the way, that middle can test the type of an action and then transform it into on or multiple others. 这样做的方式,中间可以测试一个动作的类型,然后将其转换为on或多个其他动作。 have a look here , it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests. 看看这里 ,它是相似的,即使基本上是关于动画,答案结束时有关如何使用中间件进行远程请求的一些解释。

You should have your api call be completely independent from redux. 您应该让api调用完全独立于redux。 It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. 它应该返回一个promise(就像它当前那样),在happy case中解析并拒绝一个告诉状态的参数。 Something like 就像是

if (statusAsString === '401') {
  reject({ logout: true })
}
reject({ logout: false });

Then in your action creator code you would do: 然后在你的动作创建者代码中你会做:

function fetchWithAuthAction(url, method, data) {

  return function (dispatch) {
    return fetchWithAuth(url, method, data).then(
      ({ data, headers }) => dispatch(fetchedData(data, headers)),
      ({ logout }) => {
        if(logout) {
          dispatch(UserActions.logout());
        } else {
          dispatch(fetchedDataFailed());
        }
    );
  };
}

Edit : 编辑

If you don't want to write the error handling code everywhere, you could create a helper: 如果您不想在任何地方编写错误处理代码,可以创建一个帮助程序:

function logoutOnError(promise, dispatch) {
  return promise.catch(({ logout }) => {
    if(logout) {
      dispatch(UserActions.logout());
    }
  })
}

Then you could just use it in your action creators: 然后你可以在你的动作创建者中使用它:

function fetchUsers() {
  return function (dispatch) {
    return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
  }
}

maybe you can try to use middleware to catch the error and dispatch the logout action, but in that case, the problem is you have to dispatch error in action creator which need to check the log status 也许你可以尝试使用中间件来捕获错误并调度注销操作,但在这种情况下,问题是你必须在action creator中调度错误,需要检查日志状态

api: throw the error api:抛出错误

        if (statusAsString === '401') {
          //  !!!!!-> here I need to dispatch the logout action
          throw new Error('401')
        }

action creator: catch error from api, and dispatch error action 动作创建者:从api捕获错误,并发送错误动作

    fetchSometing(ur)
      .then(...)
      .catch(err => dispatch({
        type: fetchSometingError,
        err: err 
       })

middleware: catch the error with 401 message, and dispatch logout action 中间件:用401消息捕获错误,并发送注销动作

const authMiddleware = (store) => (next) => (action) => {
  if (action.error.message === '401') {
    store.dispatch(UserActions.logout())
  }
}

You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the 您还可以使用axios(拦截器)或apisauce(监视器)并在他们前往其处理程序之前拦截所有呼叫,并在此时使用

// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status

if (response.status === '401') {
    store.dispatch(UserActions.logout())
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM