【问题标题】:React router v6 how to use `navigate` redirection in axios interceptorReact router v6如何在axios拦截器中使用`navigate`重定向
【发布时间】:2021-12-25 10:03:51
【问题描述】:
import axios from "axios";
import { useNavigate } from "react-router-dom";

export const api = axios.create({
  baseURL: "http://127.0.0.1:8000/",
  headers: {
    "content-type": "application/json",
  },
});

api.interceptors.response.use(
  function (response) {
    return response;
  },
  function (er) {
    if (axios.isAxiosError(er)) {
      if (er.response) {
        if (er.response.status == 401) {

          // Won't work
          useNavigate()("/login");

        }
      }
    }

    return Promise.reject(er);
  }
);

【问题讨论】:

  • 是的,RRDv6 现在没有暴露history 对象,useNavigate 是一个 React 钩子,仅在 React 函数组件和自定义钩子中有效使用。您的应用使用什么类型的路由器?您需要执行类似于在 RRDv4/5 中访问 history 的方式。
  • 你有没有听过我的提示/建议并自己尝试过任何事情?

标签: reactjs react-router-dom


【解决方案1】:

从 6.1.0 版本开始,React Router 有一个unstable_HistoryRouter。有了它history 现在可以再次在 React 上下文之外使用。它是这样使用的:

// lib/history.js
import { createBrowserHistory } from "history";
export default createBrowserHistory();

// App.jsx
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import history from "lib/history";

function App() {
  return (
    <HistoryRouter history={history}>
      // The rest of your app
    </HistoryRouter>
  );
}

// lib/axios.js
import history from "lib/history";
import storage from "utils/storage";

export const axios = Axios.create({})

axios.interceptors.response.use(
  (response) => {
    return response.data;
  },
  (error) => {
    if (error.response?.status === 401) {
      storage.clearToken();
      history.push("/auth/login");
      return Promise.resolve();
    }

    return Promise.reject(error);
  }
);

Source

询问unstable_-前缀是什么意思,维护者之一answered

unstable_ 前缀只是表示您可能会遇到由于 react-router 请求自身的历史版本不匹配而导致的错误。虽然实际影响可能只是创建类型问题(历史版本之间的不同类型),但也可能存在导致更微妙错误的 API 或行为更改(例如如何解析 URL,或如何生成路径)。

只要您密切关注 react-router 请求的历史版本并确保它与您的应用使用的版本同步,您就应该没有问题。我们还没有针对不匹配的保护或警告,因此我们暂时称其为不稳定。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,这就是我所做的,它对我有用 (based on an answer from a similar question for react router v4

    我在 react 路由器配置中添加了一个拦截器组件,它传递了 useNavigate 钩子的属性

    从 axios 配置文件中删除拦截器并将它们放在单独的拦截器文件中

    SetupInterceptor.js

    //place all imports here
    
    const SetupInterceptors = (navigate) => {
        axios.interceptors.response.use(
           function (response) {
               // Do something with response data
               return response;
           },
           function (error) {
               // Do something with response error
               if (error.response) {
                    if (error.response.status === 401 || error.response.status === 403) {
                        navigate('/login');
        
                    }
               }
               return Promise.reject(error);
          }
       );
    };
    
    export default SetupInterceptors;
    

    axiosconfigfile.js

    import axios from "axios";
    
    
    //axios configuration here
    
    export default axios;
    

    将 AxiosInterceptorComponent 添加到 app.js

    app.js

    import SetupInterceptors from "SetupInterceptor";
    
    function NavigateFunctionComponent(props) {
        let navigate = useNavigate();
        SetupInterceptors(navigate);
        return <></>;
    }
    
    
    function App(props) {
       return (
           <BrowserRouter>
               {<NavigateFunctionComponent />}
               <Routes>
                  {/* other routes here */}
                  <Route path="/login" element={<Login/>}></Route>
                  {/* other routes here */}
               </Routes>
           </BrowserRouter>
       );
    }
    

    【讨论】:

      【解决方案3】:

      在 RRDv6 之前的世界中,您将创建一个自定义 history 对象,以导出和导入并传递给 Router,并在外部 javascript 逻辑中导入和访问,例如 redux-thunks、axios 实用程序等.

      要在 RRDv6 中复制这一点,您还需要创建一个自定义路由器组件,以便将其传递给外部 history 对象。这是因为所有更高级别的 RRDv6 路由器都维护自己的内部历史上下文,因此我们需要复制历史实例化和状态部分并传入 props 以适应基础 Router 组件的新 API。

      import { Router } from "react-router-dom";
      
      const CustomRouter = ({ history, ...props }) => {
        const [state, setState] = useState({
          action: history.action,
          location: history.location
        });
      
        useLayoutEffect(() => history.listen(setState), [history]);
      
        return (
          <Router
            {...props}
            location={state.location}
            navigationType={state.action}
            navigator={history}
          />
        );
      };
      

      创建你需要的历史对象:

      import { createBrowserHistory } from "history";
      
      const history = createBrowserHistory();
      
      export default history;
      

      导入并传递给新的CustomRouter

      import customHistory from '../path/to/history';
      
      ...
      
      <CustomRouter history={customHistory}>
        ... app code ...
      </CustomRouter>
      

      在您的 axios 函数中导入和使用:

      import axios from "axios";
      import history from '../path/to/history';
      
      export const api = axios.create({
        baseURL: "http://127.0.0.1:8000/",
        headers: {
          "content-type": "application/json",
        },
      });
      
      api.interceptors.response.use(
        function (response) {
          return response;
        },
        function (er) {
          if (axios.isAxiosError(er)) {
            if (er.response) {
              if (er.response.status == 401) {
                history.replace("/login"); // <-- navigate
              }
            }
          }
      
          return Promise.reject(er);
        }
      );
      

      【讨论】:

        【解决方案4】:

        这不起作用的原因是您只能在 React 组件自定义钩子 中使用钩子。

        由于两者都不是,因此useNavigate() 挂钩失败。

        我的建议是将整个逻辑包装在自定义挂钩中。

        伪代码:

        import { useCallback, useEffect, useState } from "react"
        
        export default function useAsync(callback, dependencies = []) {
          const [loading, setLoading] = useState(true)
          const [error, setError] = useState()
          const [value, setValue] = useState()
        
          // Simply manage 3 different states and update them as per the results of a Promise's resolution
          // Here, we define a callback
          const callbackMemoized = useCallback(() => {
            setLoading(true)
            setError(undefined)
            setValue(undefined)
            callback()
              // ON SUCCESS -> Set the data from promise as "value"  
              .then(setValue)
              // ON FAILURE -> Set the err from promise as "error"  
              .catch((error)=> {
                  setError(error)
                  useNavigate()('/login')
               })
              // Irresp of fail or success, loading must stop after promise has ran
              .finally(() => setLoading(false))
              // This function runs everytime some dependency changes
          }, dependencies)
        
          // To run the callback function each time it itself changes i.e. when its dependencies change
          useEffect(() => {
            callbackMemoized()
          }, [callbackMemoized])
        
          return { loading, error, value }
        }
        

        在这里,只需将您的 axios 调用作为回调传递给钩子。

        对于从 v5 迁移到 v6 的 react 路由器的参考,这个video 很有帮助

        【讨论】:

          猜你喜欢
          • 2017-08-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-20
          • 2018-12-25
          • 2020-03-20
          • 2022-01-12
          • 2022-06-24
          相关资源
          最近更新 更多