Reputation: 125
I have a navbar (component navbarUser
) in the App
component, and I would like when the user is logged in, to change the navbar to show logout and profile.
When the user is logged in the navbar doesn't change and I have an useEffect
in the App
component and in NavbarUser
, but and if I make a manually refresh (F5) then the navbar change.
Where is my code problem? What is the solution in order to change dynamically the navbar?
I include a video: video
COMPONENT APP
import React, { useState, useEffect } from "react";
import logo from "./assets/logoBusca.png";
import "./App.css";
import { Route, Switch } from "wouter";
import logic from "./logic";
import Home from "./components/Home";
import Login from "./components/Login";
import Post from "./components/Post";
import NavbarUser from "./components/NavbarUser";
function App() {
const [userLogged, setUserLogged] = useState(false);
useEffect(() => {
(async () => {
const loggedIn = await logic.isUserLoggedIn;
if (loggedIn) setUserLogged(true);
})();
}, [userLogged]);
return (
<div className="App">
<header className="App-header">
<div className="images">
<div className="logo">
<a href="/">
<img src={logo} alt="logo" />
</a>
</div>
<div className="user_flags">
<NavbarUser />
</div>
</div>
</header>
<Switch>
<Route path="/" component={Home} />
<Route path="/login">{() => <Login />}</Route>
<Route path="/nuevabusqueda">{() => <Post />}</Route>
</Switch>
</div>
);
}
export default App;
COMPONENT NAVBARUSER
import React, { useState, useEffect } from "react";
import userIcon from "../../assets/userIcon.png";
import logic from "../../logic";
export default function NavbarUser() {
const [navbarUserIsLogged, setnavbarUserIsLogged] = useState(false);
useEffect(() => {
(async () => {
const loggedIn = await logic.isUserLoggedIn;
if (loggedIn) setnavbarUserIsLogged(true);
})();
}, [navbarUserIsLogged]);
return (
<>
{!navbarUserIsLogged ? (
<div className="navbar-item has-dropdown is-hoverable">
<img src={userIcon} alt="user" />
<div className="navbar-dropdown">
<a href="/login" className="navbar-item" id="item_login">
Login
</a>
<hr className="navbar-divider" />
<a href="/registro" className="navbar-item" id="item_register">
Registro
</a>
</div>
</div>
) : (
<div className="navbar-item has-dropdown is-hoverable">
<img src={userIcon} alt="user" />
<div className="navbar-dropdown">
<a href="/datos" className="navbar-item" id="item_login">
Perfil
</a>
<hr className="navbar-divider" />
<a href="/user" className="navbar-item" id="item_register">
Logout
</a>
</div>
</div>
)}
</>
);
}
COMPONENT LOGIN
import React, { useState } from "react";
import { useLocation } from "wouter";
import logic from "../../logic";
import "./index.css";
export default function Login() {
const [messageError, setMessageError] = useState("");
const [, pushLocation] = useLocation();
async function handleOnSubmit(e) {
e.preventDefault();
const {
email: { value: email },
password: { value: password }
} = e.target;
e.target.reset();
try {
await logic.loginUser(email, password);
pushLocation("/nuevabusqueda");
} catch (error) {
setMessageError(error.message);
}
}
return (
<>
{messageError && (
<div className="message-error">
<p>{messageError}</p>
</div>
)}
<section className="hero is-fullwidth">
<div className="hero-body">
<div className="container">
<div className="columns is-centered">
<div className="column is-4">
<form id="form" onSubmit={e => handleOnSubmit(e)}>
<div className="field">
<p className="control has-icons-left">
<input
className="input"
name="email"
type="email"
placeholder="Email"
required
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left">
<input
className="input"
name="password"
type="password"
placeholder="Password"
required
/>
<span className="icon is-small is-left">
<i className="fas fa-lock"></i>
</span>
</p>
</div>
<div className="field">
<p className="control">
<button className="button is-success">Login</button>
</p>
</div>
<div>
<a href="/registro">
¿Aún no estás registrado? Acceso para registarte
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</>
);
}
LOGIC
import buscasosApi from "../data";
const logic = {
set userToken(token) {
sessionStorage.userToken = token;
},
get userToken() {
if (sessionStorage.userToken === null) return null;
if (sessionStorage.userToken === undefined) return undefined;
return sessionStorage.userToken;
},
get isUserLoggedIn() {
return this.userToken;
},
loginUser(email, password) {
return (async () => {
try {
const { token } = await buscasosApi.authenticateUser(email, password);
this.userToken = token;
} catch (error) {
throw new Error(error.message);
}
})();
}
};
export default logic;
Upvotes: 1
Views: 9781
Reputation: 8316
Have a global authentication context so that all your components can successfully be aware of the changes that take place w.r.t to login/logout.
Currently what's happening is that even though the user logs in successfully, the NavbarUser
component is not aware of that happening since there is no global state in your app based on which your NavbarUser
can make a decision to change it's own state or not.
The following statement is just a one time operation anyway since navbarUserIsLogged
is local to NavbarUser
component and will only change post first render of component so this useEffect
is not essentially going to ever run again and again solely based on navbarUserIsLogged
because it's never changing. The change you require is a global state change.
useEffect(() => {
(async () => {
const loggedIn = await logic.isUserLoggedIn;
if (loggedIn) setnavbarUserIsLogged(true);
})();
}, [navbarUserIsLogged]);
React Context API solves this problem for us. Imagine having a context or global state object in which one of the key is isAuthenticated
.
Your NavbarUser
can now subscribe to the changes happening in that global state and whenever it changes, your NavbarUser
component can re-render based on it:-
/* Suppose AuthContext is our global Context for giving us the global
state or context object. We are de-structuring the isAuthenticated key
from that object here. Suppose the value of isAuthenticated changes in
global state so that will get informed to our component which uses useContext */
const {isAuthenticated} = useContext(AuthContext)
Now based on that isAuthenticated
you can directly render the Navbar for authenticated user or an unauthenticated user.
Currently even though you are saving relevant tokens inside the localStorage
, the components across your App aren't aware of those changes happening. On page refresh, the localStorage
is simply read again.
There are ways to even make your current implementation work by passing the setUserLogged
state updater function to each of your child components as a prop and triggering the update from them so that the App
and it's every child get's re-rendered but it's not a good way to go about things. Make use of Context API here.
The following answer discusses a Context API implementation - React context returns undefined on first login, but works on page refresh
As always refer React docs for more reference. Sharing the useContext
one here - https://reactjs.org/docs/hooks-reference.html#usecontext
Upvotes: 4
Reputation: 450
Create two components
<NavLoggedIn>
//Logged in links and styling goes here
<NavLoggedIn>]
<NavPublic>
//Logged out links and styling goes here
<NavPublic/>
Finally, in your Navbar component, do this:
const Navbar = () => {
return (
<>
{!isAuth() ? <NavLoggedIn /> : <NavPublic />}
</>
);
};
Upvotes: 0