I have created a hook useToken which tracks and updates the state of my access and refresh token.
When the app is started, the tokens are pulled from localStorage and are refreshed updating the values of the hook's state as well as the values in local storage.
The values within local storage update as expected but the values of the state do not reflect the change. I understand setState is asynchronous but even when awaiting or using .then() the state does not update.
This can be noticed further down where the token is used and the request fails because the access token does not match.
/hooks/useTokens.tsx:
import { useState } from "react";
export default function useTokens() {
const [tokens, setTokensState] = useState({
apiToken: localStorage.getItem("apiToken"),
refreshToken: localStorage.getItem("refreshToken"),
});
const setTokens = (tokenParams: {
apiToken: string | null;
refreshToken: string | null;
}) => {
const newTokens = { ...tokens, ...tokenParams };
setTokensState(newTokens);
newTokens.apiToken
? localStorage.setItem("apiToken", newTokens.apiToken)
: localStorage.removeItem("apiToken");
newTokens.refreshToken
? localStorage.setItem("refreshToken", newTokens.refreshToken)
: localStorage.removeItem("refreshToken");
};
return { tokens, setTokens };
};
home.tsx
const { tokens, setTokens } = useTokens();
...
refreshToken(tokens.refreshToken!).then(
({ isSuccessful, data, status }) => {
if (isSuccessful) {
// data.access_token contains new token
// tokens.xyz contains old access/refresh token even after setTokens
setTokens({
apiToken: data.access_token,
refreshToken: data.refresh_token,
});
accountsAndAuthCheck();
} else {
console.log("refresh", data, status);
setTokens({ apiToken: null, refreshToken: null });
navigate("/link", { replace: true });
}
}
);
home.tsx
...
const accountsAndAuthCheck = () => {
// tokens.apiToken has not updated
getAccounts(tokens.apiToken!).then(({ isSuccessful, data, status }) => {
if (isSuccessful) {
setIsAuthorised(1);
const _accountId = data.accounts[0].id;
setAccountId(_accountId);
} else {
if (status == 403) {
setIsAuthorised(-1);
}
}
});
};
...
If you want to fix this issue without using custom hook state, I would pass token as arguments to accountsAndAuthCheck
function and set just local storage in hook
/hooks/useTokens.tsx:
export default function useTokens() {
const setTokens = (tokenParams: {
apiToken: string | null;
refreshToken: string | null;
}) => {
const newTokens = {...tokenParams };
newTokens.apiToken
? localStorage.setItem("apiToken", newTokens.apiToken)
: localStorage.removeItem("apiToken");
newTokens.refreshToken
? localStorage.setItem("refreshToken", newTokens.refreshToken)
: localStorage.removeItem("refreshToken");
};
return { setTokens };
};
home.tsx
const { tokens, setTokens } = useTokens();
refreshToken(tokens.refreshToken!).then(
({ isSuccessful, data, status }) => {
if (isSuccessful) {
setTokens({
apiToken: data.access_token,
refreshToken: data.refresh_token,
});
accountsAndAuthCheck(data.access_token, data.refresh_token);
} else {
console.log("refresh", data, status);
setTokens({ apiToken: null, refreshToken: null });
navigate("/link", { replace: true });
}
}
);
const accountsAndAuthCheck = (apiToken, refreshToken) => {
getAccounts(apiToken!).then(({ isSuccessful, data, status }) => {
if (isSuccessful) {
setIsAuthorised(1);
const _accountId = data.accounts[0].id;
setAccountId(_accountId);
} else {
if (status == 403) {
setIsAuthorised(-1);
}
}
});
};
If you want to keep state in your custom hook then I would suggest you to try triggering accountsAndAuthCheck
function with change of token
state in useEffect