Search code examples
javascriptreactjsnode.jsexpressreact-router-dom

useNavigate don't work with HOC Component


This is my first question. I am self-studying React.js, Node.js, and Express. For this reason, I am trying to create my own website where a user can register, log in, and access the page with their data only if the login is successful.

If the login is successful, a token is created using the user's email as the payload. Additionally, I want to use a Higher Order Component (HOC)

Protect.js

import React, { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import axios from 'axios';

const Protect = ({ component: Component }) => {
  const [authenticated, setAuthenticated] = useState(false);

  useEffect(() => {
    
    const checkAuth = async () => {
      try {
        
        const response = await axios.get('http://localhost:5000/checkAuth', { withCredentials: true });
        console.log('Protected GET Response:', response.data);
        
        if (response.status === 200) {
          setAuthenticated(true);
        } else {
          setAuthenticated(false);
        }
      } catch (error) {
        console.error('Protect: Error during auth', error.response);
        setAuthenticated(false);
      }
    };


    checkAuth();
  }, []);

return authenticated ? <Component /> : <Navigate to="/Login" />;

};

export default Protect;

so that in my App.js file, I have something like:

<Route
    path="/ProtectedPage"
    element={<Protect component={<ProtectedPage />} />}
/>

In the handleFormSubmit function of FormLogin, I added

      const handleFormSubmit = async (event) => {
    event.preventDefault();
  
    const formData = {
      email: email,
      password: password,
    };
  
    try {
      const response = await axios.post('http://localhost:5000/login', formData, { withCredentials: true });
      console.log("Client: Server Says: ", response.data);
      console.log("Client:  Login OK");
  
      navigate('/ProtectedPage');
    } catch (error) {
      console.log('Server Error:', error);
    }
  };

It might be useful to know that Protected makes a GET request to

const verifyToken = (req, res, next) => {
  const token = req.cookies.token;

  if (!token) {
    res.status(401).send('Access Denied');
  } else {
    jwt.verify(token, secret, function (err, decoded) {
      if (err) {
        res.status(401).send('No Valid Token!');
      } else {
        req.email = decoded.email;
        req.token = token;
        console.log('verifyToken: ', req.email);
        next();
      }
    });
  }
};

app.get('/checkAuth', verifyToken, (req, res) => {
    res.sendStatus(200).send({ authenticated: true, email: req.email, token: req.token});
});

You can view my code at this link: https://github.com/CiccioLagXCVIII/MyOwnSite.git

But even if the login is successful, I don't get any errors or redirection to /ProtectedPage

Thanks to anyone who will respond.

I have already tried using console.log to verify if the data is received and sent correctly, and it is. My issue is that I don't see any errors (I only see the logs that I have entered myself for debugging) in the console when the login is successful, but the redirection doesn't happen.


Solution

  • First just a clarification on nomenclature: Protect isn't a Higher Order Component, e.g. it's not a function that takes a React component as an argument and returns a decorated React component, it is just a regular React component that takes a component prop.

    The issue is that Protect uses an initial authenticated value that matches unauthenticated users and on the initial render will redirect users to the "/login" route.

    Start from an "unknown" authentication state and wait for the checkAuth code to complete and update the authenticated state prior to rendering the protected content or redirecting.

    The component prop is already JSX, as passed from component={<PaginaProtetta />} in the Route, so Protect should simply return component or the redirect.

    Example:

    const Protect = ({ component }) => {
      const [authenticated, setAuthenticated] = useState(); // <-- initially undefined
    
      useEffect(() => {
        const checkAuth = async () => {
          try {
            const response = await axios.get(
              'http://localhost:5000/checkAuth',
              { withCredentials: true }
            );
            setAuthenticated(response.status === 200);
          } catch (error) {
            setAuthenticated(false);
          }
        };
    
        checkAuth();
      }, []);
    
      if (authenticated === undefined) {
        return null; // <-- or loading indicator/spinner/etc
      }
    
      return authenticated ? component : <Navigate to="/Login" replace />;
    };
    
    const App = () => {
      return (
        <Router>
          <Routes>
            <Route path="/Login" element={<FormLogin />} />
            <Route
              path="/ProtectedPage"
              element={<Protect component={<PaginaProtetta />} />}
            />
          </Routes>
        </Router>
      );
    };