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