I can see the content on the home page and navigation menu at the top. When I click on the Login link (or Register or My Data), I only see the navigation menu, the Home page content disappears as expected.
It's probably a problem with the <Route>
part, I had some trouble getting that to work without errors being displayed. My package.json file has this: "react-router-dom": "^6.16.0"
The Developer Tools debug console doesn't show any errors.
VS Code compiled it fine without any errors to.
Here's the App.tsx file:
import LoginPage from './user/LoginPage';
import RegisterPage from './user/RegisterPage';
import MyDataPage from './user/MyDataPage';
import PublicRoute from './user/routes/PublicRoute'
import PrivateRoute from './user/routes/PrivateRoute'
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import './App.css';
import React from 'react';
function App() {
return (
<Router>
<div className="container">
<nav className="nav">
<Link to="/" className="button">Home</Link>
<Link to="/user/login" className="button">Login</Link>
<Link to="/user/register" className="button">Register</Link>
<Link to="/user/mydata" className="button">My Data</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/login/*" element={<PublicRoute path="/user/login/*" element={<LoginPage />} />} />
<Route path="/user/register/*" element={<PublicRoute path="/user/register/*" element={<RegisterPage />} />} />
<Route path="/user/mydata/*" element={<PrivateRoute path="/user/mydata/*" element={<MyDataPage />} />} />
</Routes>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h1>Welcome to my app</h1>
<p>This is the home page</p>
</div>
);
}
export default App;
Here's the code for PublicRoute.tsx:
import { ComponentType } from 'react';
import { getToken } from '../AuthService'
import { Navigate, Route, Routes } from 'react-router-dom';
import React from 'react';
interface PublicRouteProps {
path: string;
element: React.ReactElement;
}
function PublicRoute({element: Component, ...rest}: PublicRouteProps): JSX.Element {
return (
<Routes>
<Route
{...rest}
element={
!getToken() ? Component : <Navigate to="/user/mydata" />
}
/>
</Routes>
)
}
export default PublicRoute;
Here's the code for Login.tsx:
import React, { useState } from 'react'
import axios from 'axios'
import { setUserSession } from './AuthService'
import { useNavigate } from 'react-router-dom';
const loginUrl = "https://{somewhere}.execute-api.us-east-1.amazonaws.com/prod/login"
function LoginPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const navigate = useNavigate();
const submitHandler = (event: React.FormEvent) => {
event.preventDefault();
if(username.trim() === '' || password.trim() === '') {
setErrorMessage('Both username and password are required.');
return;
}
setErrorMessage('');
console.log("submit button is pressed.")
const requestConfig = {
headers: {
'x-api-key': '{some awesome api key}'
}
}
const requestBody = {
username: username,
password: password
}
axios.post(loginUrl, requestBody, requestConfig).then(response => {
setUserSession(response.data.user, response.data.token);
navigate('/user/mydata')
}).catch(error => {
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
setErrorMessage(error.response.data.message);
setSuccessMessage('');
} else {
setErrorMessage('Sorry...backend server error. Please try again later. Error=' + error);
setSuccessMessage('');
}
})
}
return (
<div>
<h1>Login here</h1>
<form onSubmit={submitHandler}>
<fieldset>
<legend>Loing Credentials</legend>
<div className="row">
<div className="col-sm-12 col-md-3">
<label >Username:</label>
<input type="text" value={username} onChange={event => setUsername(event.target.value)} /> <br />
</div>
<div className="col-sm-12 col-md-3">
<label >Password:</label>
<input type="password" value={password} onChange={event => setPassword(event.target.value)} /> <br />
</div>
</div>
</fieldset>
<div className="row">
<div className="col-sm-12">
<input type="submit" value="Login" />
</div>
</div>
</form>
{errorMessage && <p className="card error">{errorMessage}</p>}
{successMessage && <p className="card">{successMessage}</p>}
</div>
)
}
export default LoginPage;
The PublicRoute
route component renders descendent routes and descendent routes build their routes relative to the parent route. The PublicRoute
component passes through the path
prop to the descendent route.
For example this route for the LoginPage
.
function PublicRoute({
element: Component, ...rest
}: PublicRouteProps): JSX.Element {
return (
<Routes>
<Route
{...rest} // <-- path passed here
element={!getToken()
? Component
: <Navigate to="/user/mydata" />
}
/>
</Routes>
)
}
<Route
path="/user/login/*"
element={(
<PublicRoute
path="/user/login/*" // <-- descendent route path passed
element={<LoginPage />}
/>
)}
/>
This means that Component
is rendered on path "/user/login/user/login"
, which is most certainly not what you intended.
You could likely resolve the issue by passing "/"
as the descendent route path so the path resolves as "/user/login"
, e.g.:
<Route
path="/user/login/*"
element={<PublicRoute path="/" element={<LoginPage />} />}
/>
You are overcomplicating the routing. Convert PublicRoute
and PrivateRoute
into layout routes, e.g. routed components that render an Outlet
for their nested routes to render their content into.
See Layout Routes and Outlets for more details.
import { Navigate, Outlet } from 'react-router-dom';
function PublicRoute(): JSX.Element {
return !getToken()
? <Outlet />
: <Navigate to="/user/mydata" replace />
}
function App() {
return (
<Router>
<div className="container">
<nav className="nav">
<Link to="/" className="button">Home</Link>
<Link to="/user/login" className="button">Login</Link>
<Link to="/user/register" className="button">Register</Link>
<Link to="/user/mydata" className="button">My Data</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route element={<PublicRoute />}>
<Route path="/user/login" element={<LoginPage />} />
<Route path="/user/register" element={<RegisterPage />} />
</Route>
<Route element={<PrivateRoute />}>
<Route path="/user/mydata" element={<MyDataPage />} />
</Route>
</Routes>
</div>
</Router>
);
}