Search code examples
reactjsaxiosreact-hooksinterceptorreact-context

React Hooks: Display global spinner using axios interceptor?


I would like to add a Loader component to be rendered whenever an API call is being made in React. I want to use react context + hooks instead of redux.

As the rules of react hooks say, we should not use react hooks outside the react component. But I need to dispatch the SHOW_LOADER and HIDE_LOADER inside the Axios interceptor as below.

Is there a way to achieve this?

import axios from "axios";
axios.interceptors.request.use(
  config => {
    dispatch({
    type: "SHOW_LOADER"
})
    return config;
  },
  error => {
     dispatch({
    type: "HIDE_LOADER"
})
    return Promise.reject(error);
  }
);

axios.interceptors.response.use(
  response => {
    dispatch({
    type: "HIDE_LOADER"
})
    return response;
  },
  error => {
    dispatch({
    type: "HIDE_LOADER"
})
    return Promise.reject(error);
  }
);
function GlobalLoader(){
    const [state,dispatch] = useContext(LoaderContext);
    return(
        <div>
            {
                state.loadStatus &&
                    <Loader
                    type = "Puff"
                    color = "#00BFFF"
                    height = {100}
                    width = {100}
                    timeout = {3000} />
            }
        </div>
    );
}

export default GlobalLoader;

Please let me know if more information is required.:)


Solution

  • Create an axios instance using axios.create(config). Use this instance inside useEffect() to add interceptors that can effect the state (reducer is an overkill here). Now use the instance everywhere, and the interceptors will cause a change in the state.

    Note: Since multiple requests can start/and or end, you should use a counter. Increment on request, and decrement on response. If the counter is not 0, the application is loading.

    const { useState, useMemo, useEffect } = React;
    
    const ax = axios.create(); // export this and use it in all your components
    
    const useAxiosLoader = () => {
      const [counter, setCounter] = useState(0);
      
      useEffect(() => {
        const inc = mod => setCounter(c => c + mod);
        
        const handleRequest = config => (inc(1), config);
        const handleResponse = response => (inc(-1), response);
        const handleError = error => (inc(-1), Promise.reject(error));
      
        // add request interceptors
        const reqInterceptor = ax.interceptors.request.use(handleRequest, handleError);
        // add response interceptors
        const resInterceptor = ax.interceptors.response.use(handleResponse, handleError);
        return () => {
          // remove all intercepts when done
          ax.interceptors.request.eject(reqInterceptor);
          ax.interceptors.response.eject(resInterceptor);
        };
      }, []);
      
      return counter > 0;
    };
    
    const GlobalLoader = () => {
      const loading = useAxiosLoader();
    
      return(
        <div>
        {
          loading ? 'loading' : 'not loading'
        }
        </div>
      );
    }
    
    const callApi = (err) => ax.get(err ? 'https://asdf' : 'https://www.boredapi.com/api/activity')
    
    // make a request by using the axios instance
    setTimeout(() => {
      callApi();
      callApi(true);
      callApi();
    }, 1000);
    
    ReactDOM.render(
      <GlobalLoader />,
      root
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    
    <div id="root"></div>

    Old version:

    const { useState, useMemo, useEffect } = React;
    
    const ax = axios.create(); // export this and use it in all your components
    
    const useAxiosLoader = () => {
      const [counter, setCounter] = useState(0);
        
      const interceptors = useMemo(() => {
        const inc = () => setCounter(counter => counter + 1);
        const dec = () => setCounter(counter => counter - 1);
        
        return ({
          request: config => (inc(), config),
          response: response => (dec(), response),
          error: error => (dec(), Promise.reject(error)),
        });
      }, []); // create the interceptors
      
      useEffect(() => {
        // add request interceptors
        const reqInterceptor = ax.interceptors.request.use(interceptors.request, interceptors.error);
        // add response interceptors
        const resInterceptor = ax.interceptors.response.use(interceptors.response, interceptors.error);
        return () => {
          // remove all intercepts when done
          ax.interceptors.request.eject(reqInterceptor);
          ax.interceptors.response.eject(resInterceptor);
        };
      }, [interceptors]);
      
      return [counter > 0];
    };
    
    const GlobalLoader = () => {
        const [loading] = useAxiosLoader();
        
        return(
          <div>
          {
            loading ? 'loading' : 'not loading'
          }
          </div>
        );
    }
    
    const callApi = (err) => ax.get(err ? 'https://asdf' : 'https://www.boredapi.com/api/activity')
    
    // make a request by using the axios instance
    setTimeout(() => {
      callApi();
      callApi(true);
      callApi();
    }, 1000);
    
    ReactDOM.render(
      <GlobalLoader />,
      root
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    
    <div id="root"></div>