Lesson Redirect to Dashboard if logged in, timeline 02:00
Oh my gooooood. The more I watch the less I like.... Mama Samba is soooooooo impatient and unreasonable. I have just implemented the feature of fetching a user by id. Instead of using the null in the initial state in AuthContext.jsx - I used a blank object as a kind of a guest with ROLE_GUEST. I decided to go this way so that Sidebar.jsx is loaded properly.
AuthContext.jsx
import { createContext, useContext, useEffect, useState } from "react";
import { executeLogin, getCustomerById } from "../../services/client.js";
import jwtDecode from "jwt-decode";
import { redirect } from "react-router-dom";
const AuthContext = createContext({});
const AuthProvider = ({ children }) => {
let defaultCustomer = {
name: "Viewer",
email: "",
age: undefined,
gender: "",
roles: ["ROLE_GUEST"]
};
const [customer, setCustomer] = useState(defaultCustomer);
useEffect(() => {
getCustomer().then(data => setCustomer({ ...data }))
}, []);
const getCustomer = async () => {
const token = localStorage.getItem("access_token");
const id = localStorage.getItem("customer_id");
if (!token || !id) {
return logout();
}
const { data } = await getCustomerById(id)
const decodedJwt = jwtDecode(token);
if (!data.email === decodedJwt.sub) {
logout();
}
return data;
}
const login = async submitEmailPassword => new Promise(
(resolve, reject) => executeLogin(submitEmailPassword)
.then(res => {
const jwt = res.headers["authorization"];
localStorage.setItem("access_token", jwt);
localStorage.setItem("customer_id", res.data.customerDto.id);
getCustomer().then(fetched => {
setCustomer({ ...fetched })
resolve(res);
});
}).catch(err => {
reject(err);
})
);
const logout = () => {
localStorage.removeItem("access_token");
localStorage.removeItem("customer_id");
setCustomer(defaultCustomer);
redirect("/")
};
const isCustomerAuthenticated = () => {
const token = localStorage.getItem("access_token");
const id = localStorage.getItem("customer_id");
if (!token || !id) {
logout();
return false
}
const { exp } = jwtDecode(token);
if (Date.now() > exp * 1000) {
logout();
return false;
}
return true;
};
return (
<AuthContext.Provider value={{
customer,
login,
logout,
isCustomerAuthenticated
}}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
export default AuthProvider;
export const getCustomerById = async id => {
try {
return await axios.get(
`${import.meta.env.VITE_API_BASE_URL}/api/v1/customers/${id}`,
getTokenFromLocalStorage()
);
} catch (e) {
throw e;
}
}
Each request I take the user's id from the localStorage, fetch the user from the database by the id and place the user into the state. Before that I retrieve the user's email from JWT and compare it with the customer's email - if they are the same - ok. If any of localStorage values have been removed from the client's side (jwt or user id) I redirect the user to the login page and delete any data in their localStorage.
What he does in the Login component is he checks if the user is null (state). The difference is he doesn't fetch the user from the database (there is no async request) and it seems to have worked perfectly well. In my case, by the time it checks the user it is yet GIMMICK (a guest! or it would've been null if I have done the same as he does in the video). Anyway, what we actually need to check is not the user's state, but if the user is authenticated:
Login.jsx
const { isCustomerAuthenticated } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (isCustomerAuthenticated()) {
navigate("dashboard")
}
}, [])
or, an alternative way....:
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem("access_token");
const id = localStorage.getItem("customer_id");
if (token && id) {
navigate("dashboard")
}
}, [])
if you're going to implement the same, there are extra two pieces of code:
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
login(values)
.then(res => {
navigate("dashboard");
}).catch(err => notifyOnFailure(err.code, err.response?.data.message)
).finally(() => setSubmitting(false));
}}
onSubmit={
(customer, { setSubmitting }) => {
setSubmitting(true);
registerCustomer(customer)
.then(res => {
notifyOnSuccess("Customer saved", `${customer.name} was saved`);
return login({
username: customer.email,
password: customer.password
})
}).then(res => location.reload())
.catch(err => notifyOnFailure(err.code, err.response.data.error))
.finally(() => setSubmitting(false))
}
}
One is used when a user logs in to their account, another one is when a user signs up.