score:4

Accepted answer

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 Query

More Query from same tag