javascriptreactjsnode.jsexpressmern

Page refresh on token-authenticated route redirects to 404 page in React


I have a simple app where there is a login page and upon submitting the form, it makes a request to the backend which returns a token upon successful authentication, which then redirects to internal dashboard page. There is also another page called settings and another is the NotFound (404) page.

After logging in, when I'm in dashboard page or settings page, if I refresh, it takes me to 404 page whereas it should stay at the page I'm in. Does anybody know what the issue could be?

Here's the code:

App.js

const App = () => {
    const [isAuthenticated, setIsAuthenticated] = useState(false);

    const handleLogin = (token) => {
        localStorage.setItem('token', token);

        setIsAuthenticated(true);
    };

    useEffect(() => {
        const token = localStorage.getItem('token');

        setIsAuthenticated(!!token);
    }, []);

    return (
        <Router>
            {
                isAuthenticated ? (
                    <Routes>
                        <Route path="/dashboard" element={
                            <Layout>
                                <Dashboard />
                            </Layout>
                        } />
                        <Route path="/settings" element={
                            <Layout>
                                <Settings />
                            </Layout>
                        } />
                        <Route path="*" element={<Layout><NotFound /></Layout>} />
                    </Routes>
                ) : (
                    <Routes>
                        <Route path="/" element={<Login onLogin={handleLogin} />} />
                        <Route path="*" element={<Navigate to="/" />} />
                    </Routes>
                )
            }
        </Router>
    );
}
Login.js

const Login = ({ onLogin }) => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const navigate = useNavigate();

    const handleSubmit = async (e) => {
        e.preventDefault();

        const response = await fetch('/api/login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ username, password }),
        });

        if (response.ok) {
            const result = await response.json();

            if (result.success) {
                localStorage.setItem('token', result.token);

                onLogin(result.token);
                navigate('/dashboard');
            }
        }
    };

    return (
        <div class="container">
            <div class="formContainer">
                <h1>LOGIN</h1>

                <form onSubmit={handleSubmit}>
                    <input
                        type="text"
                        value={username}
                        placeholder="username"
                        onChange={(e) => setUsername(e.target.value)}
                        required
                    />

                    <input
                        type="password"
                        value={password}
                        placeholder="password"
                        onChange={(e) => setPassword(e.target.value)}
                        required
                    />

                    <button type="submit" className="login-button">
                        Login
                    </button>
                </form>
            </div>
        </div>
    );
}

Solution

  • The setState or for your case, setIsAuthenticated from useState is asynchronously set in React. So, initially your isAuthenticated is false, it doesn't wait for your setIsAuthenticated.


    Possible fixes:

    1. Make the initial state null.
    const App = () => {
        const [isAuthenticated, setIsAuthenticated] = useState(null);
    
        const handleLogin = (token) => {
            localStorage.setItem('token', token);
    
            setIsAuthenticated(true);
        };
    
        useEffect(() => {
            if (isAuthenticated !== null) return;
            const token = localStorage.getItem('token');
    
            setIsAuthenticated(!!token);
        }, []);
    
    ...
    

    So, in this case, isAuthenticated is not false or defined, so it will then proceed to check the login status, if it's false, then automatically goes to login page.

    1. Directly assign localstorage in initial state:
     const [isAuthenticated, setIsAuthenticated] = useState(
            !!localStorage.getItem('token') // Initialize from localStorage
        );
    

    In this case, the token is fetched directly from the localstorage on initial load.