ccnat
ccnat

Reputation: 125

react how to change Navbar dynamically when you are logged in

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

Answers (2)

Lakshya Thakur
Lakshya Thakur

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

theabrar
theabrar

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

Related Questions