Home > OS >  API request doesn't receive access token after page refresh in React
API request doesn't receive access token after page refresh in React

Time:12-13

I'm making a React application using the Pet-finder API. I store my access token inside a context. When i render a specific animals page it renders fine (most of the time, assuming because of the same problem), but when i refresh the page it throws an axios error with the status: 401 for which the API docs refer as Access was denied due to invalid credentials. When i check the headers it contains Authorization: "undefined undefined" where the token type and the token itself is supposed to be.

The partial fix i have found is adding token to the dependency array of the useEffect I use to make the request but the error still pops up in the console on refresh and the data loads right after. Also this is the only page I'm making a request on immediately after rendering (maybe it has something to do with the issue).

TokenContext.jsx

const TokenContext = createContext();

export const TokenProvider = ({ children }) => {
  const [token, setToken] = useState({});
  let location = useLocation();

  const fetchToken = async () => {
    await axios
      .get("http://localhost:3001/fetchToken")
      .then((res) => {
        const newToken = {
          token: res.data.access_token,
          tokenType: res.data.token_type,
          expires: new Date().getTime()   res.data.expires_in * 1000,
        };

        localStorage.setItem("token", JSON.stringify(newToken));
      })
      .catch((error) => {
        console.log(error);
      });
  };

  useEffect(() => {
    const foundToken = JSON.parse(localStorage.getItem("token"));

    if (!foundToken || token.expires - new Date().getTime() < 10) {
      fetchToken();
    } else {
      setToken(foundToken);
    }
  }, [location.pathname]);

  return (
    <TokenContext.Provider value={{ token }}>{children}</TokenContext.Provider>
  );
};

export default TokenContext;

Pet.jsx

const Pet = () => {
  const { id } = useParams();
  const { token } = useContext(TokenContext);
  const [isLoading, setIsLoading] = useState(false);
  const [pet, setPet] = useState({});

  useEffect(() => {
    setIsLoading(true);

    petFinderApi
      .get(`/animals/${id}`, {
        headers: {
          Authorization: `${token.tokenType} ${token.token}`,
        },
      })
      .then((res) => {
        setPet(res.data.animal);
        setIsLoading(false);
      })
      .catch((error) => {
        console.log(error);
      });
  }, [token]);

  if (!pet) {
    return (
      isLoading ?? (
        //Spinner renders here
      )
    );
  } else {
    return (
//Stuff will render here
)
  }
};

export default Pet;

CodePudding user response:

Token is initially an empty object:

const [token, setToken] = useState({});

While you are fetching the token in the background, the token value is passed to TokenContext:

<TokenContext.Provider value={{ token }}>{children}</TokenContext.Provider>

During the first rendering, the following useEffect will still be executed.

useEffect(() => {
// ...
}, [token]); // {}

Try adding a check to see if token is undefined/empty object in the useEffect?

  useEffect(() => {
    setIsLoading(true);
    
    if(token && Object.keys(token).length !== 0){
       // grab a pet
    }
  }, [token]);

Instead of writing

  if (!pet) {
    return (
      isLoading ?? (
        //Spinner renders here
      )
    );

You can also use React.Suspense to display the spinner.

e.g.

import { Suspense } from 'react';

//... other code

return (
   <Suspense fallback={<MySpinner />}>
      <MyComponentToDisplayWhenPetIsFetched pet={pet} />
   </Suspense>
);

This tutorial explains Suspense pretty well

  • Related