1

I have created an axios interceptor for my service, scenario is if specific api calls fails. It will redirect to error component. But it gives me error Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Here is my code for agent.ts:

   axios.defaults.baseURL = "https://localhost:5001/api/";

const responseBody = (response: AxiosResponse) => response.data;

    axios.interceptors.response.use(
      (response) => {
        return response;
      },
      (error: AxiosError) => {
        const { data, status }: { data: any; status: any } = error.response!;
        const navigate = useNavigate();
        switch (status) {
          case 500:
            //toast.error(data.title);
            navigate("server-error");
            break;
          default:
            break;
        }
        return Promise.reject(error.response);
      }
    );

const request = {
  get: (url: string) => axios.get(url).then(responseBody),
};

const Catalog = {
  list: () => request.get("products"),
};

const agent = {
  Catalog,
};

And in my component, here is the code for calling the request:

    useEffect(() => {
    agent.Catalog.list().then((products) => setProducts(products));
  }, []);
Asdf1234567
  • 476
  • 1
  • 10
  • 25

1 Answers1

2

As mentioned in the error, hooks can only be used inside a function component. By using useNavigate inside a function that isn't a function component would simply yield an erroneous result.

You can create a hook that intercepts requests and use useNavigate within the resulting interception.

The code below demonstrates how to consume an axios interceptor in a react component.

Working codesandbox example: Please note that you need to open the web app in a new window instead of the codesandox iframe, since I'm using msw to simulate a 500 status code request. You can visit the non-iframe version in this link.

Navigate to the "Page with Error" link, it should send a request to a URL that responds a 500 status code. The axios interceptor should direct the page back to the / route.

// AxiosNavigation.js
import { useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';

export function useAxiosNavigation() {
  // Use useRef to prevent a re-render in the useEffect.
  // A ref, cannot be used as a useEffect dependency, hence,
  // your linters shouldn't complain about missing dependencies.
  const navRef = useRef(useNavigate());

  useEffect(() => {
    const intercetpor = axios.interceptors.response.use(
      (response) => response,
      (error) => {
        switch (error?.response?.status) {
          case 500:
            navRef.current('/');
            break;
          default:
        }
        return Promise.reject(error);
      }
    );

    return () => {
      axios.interceptors.response.eject(intercetpor);
    };
  }, []);
}

export default function AxiosNavigation() {
  useAxiosNavigation();
  return <></>;
}

To use the component above, we need to add it inside a router, the case below uses MemoryRouter but you can freely change it to BrowserRouter if you want to.

// App.js
import { MemoryRouter, Routes, Route, Link } from 'react-router-dom';
import AxiosNavigation from './AxiosNavigation';
import PageWithError from './Page-With-Error';

export default function App() {
  return (
    <main>
      <MemoryRouter>
        <AxiosNavigation />

        <header>
          <nav>
            <Link to="/">Home</Link>
            <Link to="/about">About</Link>
            <Link to="/page-with-error">Page with error</Link>
          </nav>
        </header>

        <article>
          <Routes>
            <Route path="/" element={<>Home Page</>} />
            <Route path="/about" element={<>About Page</>} />
            <Route path="/page-with-error" element={<PageWithError />} />
          </Routes>
        </article>
      </MemoryRouter>
    </main>
  );
}

The PageWithError component also has to be defined, it simply sends a request to an API that returns 500 status code. As described in the axios interceptor hook we implemented above, it simply goes to the root page, /.

// PageWithError.js
import axios from 'axios';
import { useState, useEffect } from 'react';

export default function PageWithError() {
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    setLoading(true);
    axios
      .get('/api-500-error')
      .catch((error) => {
        console.log('caught', error.response);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  return <>Page With Error: {loading && 'going to home page...'}</>;
}
ryeballar
  • 29,658
  • 10
  • 65
  • 74