Home > Software design >  Update navbar after success login or logout redirection
Update navbar after success login or logout redirection

Time:04-22

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.

  • Related