I have a simple authentication app in React, the problem is that when the user logs in, he is redirected to his profile but my navbar component is not rendering correctly. Only applies the changes after I refresh the page but not when is directly redirected by the request.
App Component:
function App() {
const auth = useAuth();
return (
<>
<Router>
{auth.auth ? <Navbar/> : <NotAuthNavbar/>}
<Routes>
<Route path="/" element={<Dashboard/>}/>
<Route path="/login" element={<Login/>}/>
<Route path="/signup" element={<Register/>}/>
<Route path="/profile"
element={
<PrivateRoute>
<Profile/>
</PrivateRoute>
}
/>
<Route path="/confirm-register/:tokenConfirm" element={<ConfirmRegister/>}/>
<Route path="/registered" element={<RegisterMessage/>}/>
</Routes>
</Router>
</>
);
}
export default App;
Auth Custom Hook:
const useAuth = () => {
const [auth,setAuth] = useState(null);
const [user,setUser] = useState({});
const isAuth = async() => {
await axios.get('http://localhost:5000/api/logged-user/',{withCredentials:true})
.then(res => {
setUser(res.data);
setAuth(true);
})
.catch(error => {
setAuth(false);
});
}
useEffect(() => {
isAuth();
},[auth])
return{
auth:auth,
user:user
}
}
export default useAuth;
Navbar and NotAuthNavbar:
///Navbar.jsx
const Navbar = () => {
const auth = useAuth();
const navigate = useNavigate();
const logout = async() =>{
await axios.get("http://localhost:5000/api/logout/",{withCredentials:true})
.then(res => {
console.log(res.data);
navigate('/login');
return;
})
}
return(
<section>
<div className="navbar">
<ul className="navbar-menu">
<li><Link to={"/dashboard"}>Dashboard</Link></li>
<li><Link to={"/profile"}>Welcome {auth.user.username}</Link></li>
<li><button type='button' onClick={logout}>Logout</button></li>
</ul>
</div>
<Outlet />
</section>
)
}
export default Navbar;
/// NotAuthNavbar.jsx
const NotAuthNavbar = () => {
return(
<section>
<div className="navbar">
<ul className="navbar-menu">
<li><Link to={"/dashboard"}>Dashboard</Link></li>
<li><Link to={"/signup"}>Sign Up</Link></li>
<li><Link to={"/login"}>Sign In</Link></li>
</ul>
</div>
<Outlet />
</section>
)
}
export default NotAuthNavbar;
When user log in, I redirect to his profile with Navigate by react-router-dom.
I don't know how to update the navbar after login and logout. I can't stop thinking there is a small details that I don't see, but I'm have been stucked in this for few hours.
Thanks in advance.
EDIT
This is the function of login
const login = async(e) => {
e.preventDefault();
await axios.post("http://localhost:5000/api/signin/",{
email:email.email,
password:password.password
},{
withCredentials:true,
})
.then(res => {
setIsSubmitted(true);
if(res.status === 200){
alert('!LOGGED');
navigate('/profile');
return res.data;
}
})
.catch(error => {
let parsedErrors = [];
parsedErrors = JSON.parse(error.request.response);
setHandleErrors(parsedErrors);
setIsSubmitted(true);
})
}
CodePudding user response:
Issue
React hooks don't share state.
Solution
Move the useAuth
state into a React context and provide that to the app. The useAuth
hook should then use the useContext
hook to access the single auth state.
Example:
Create an AuthContext
, provider, and hook.
import { createContext, useContext, useEffect, useState } from 'react';
const AuthContext = createContext({
auth: null,
setAuth: () => {},
user: null,
});
export useAuth = () => useContext(AuthContext);
const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState(null);
const [user, setUser] = useState(null);
useEffect(() => {
const isAuth = async () => {
try {
const res await axios.get(
'http://localhost:5000/api/logged-user/',
{ withCredentials: true }
);
setUser(res.data);
} catch(error) {
setUser(null);
};
};
isAuth();
}, [auth]);
return (
<AuthContext.Provider value={{ auth, setAuth, user }}>
{children}
</AuthContext.Provider>
);
};
export default AuthProvider;
Wrap the App
component with the AuthProvider
component.
import AuthProvider from "../path/to/AuthContext";
...
<AuthProvider>
<App />
</AuthProvider>
Now that there's a single auth
and user
state the useAuth
hooks all reference the same state.
App
function App() {
const { auth } = useAuth();
return (
<Router>
{auth ? <Navbar /> : <NotAuthNavbar />}
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Register />} />
<Route
path="/profile"
element={(
<PrivateRoute>
<Profile />
</PrivateRoute>
)}
/>
<Route path="/confirm-register/:tokenConfirm" element={<ConfirmRegister />} />
<Route path="/registered" element={<RegisterMessage />} />
</Routes>
</Router>
);
}
Navbar
Access the setAuth
updater function to set auth
false upon successful logout.
const Navbar = () => {
const { setAuth, user } = useAuth();
const navigate = useNavigate();
const logout = async () => {
const res await axios.get(
"http://localhost:5000/api/logout/",
{ withCredentials: true }
);
console.log(res.data);
setAuth(false);
navigate('/login');
}
return(
<section>
<div className="navbar">
<ul className="navbar-menu">
<li><Link to={"/dashboard"}>Dashboard</Link></li>
<li><Link to={"/profile"}>Welcome {user.username}</Link></li>
<li><button type='button' onClick={logout}>Logout</button></li>
</ul>
</div>
<Outlet />
</section>
);
};
Component using login
function
Use the useAuth
hook to access the setAuth
updater function and set auth
true upon successful authentication.
const { setAuth } = useAuth();
...
const login = async (e) => {
e.preventDefault();
try {
const res = await axios.post(
"http://localhost:5000/api/signin/",
{
email: email.email,
password: password.password
},
{
withCredentials: true,
}
);
if (res.status === 200) {
alert('!LOGGED');
setAuth(true);
navigate('/profile');
}
} catch(error) {
setHandleErrors(JSON.parse(error.request.response));
} finally {
setIsSubmitted(true);
}
}
Note: It's anti-pattern to mix async/await
with Promise chains. Generally you should select one or the other. I've used async/await
with try/catch
to handle rejected promises and other errors that may occur.