I have a React Router where some Routes check if the user is logged in. The function calls the backend and returns either true or false. This part is working fine but when i call the function to either render the requested component or the redirect, it always renders the redirect.
function requireAuth() {
axiosInst.post("http://localhost:3001/checkLogged")
.then(function (response) {
if (response.data.user) {
return true
}else{
return false
}
})
}
<Router>
<>
<Navigation/>
<Switch>
<Route path="/login/" exact component={Login} ></Route>
<Route path="/register/" exact component={Register} />
<Route path="/" exact component={Feed} />
<Route path="/articles/" exact component={Feed} />
<Route path="/profile/" >
{requireAuth() ? <Profile/> : <Redirect to="/" />}
</Route>
<Route path="/settings/" component={Settings} />
<Route path="/write/" >
{requireAuth() ? <Write/> : <Redirect to="/" />}
</Route>
<Route path="/search/" component={Search} />
<Route path="/articles/:id" component={ArticleView} />
</Switch>
</>
</Router>
This happens because requireAuth is async and when it returns the boolean, React already rendered the Redirect.
Why don't you use a state variable? Something like:
const [auth, setAuth] = useState(false);
...
function requireAuth() {
axiosInst.post("http://localhost:3001/checkLogged")
.then(function (response) {
if (response.data.user) {
setAuth(true);
return true;
}else{
setAuth(false);
return false;
}
})
}
...
<Router>
<>
<Navigation/>
<Switch>
<Route path="/login/" exact component={Login} ></Route>
<Route path="/register/" exact component={Register} />
<Route path="/" exact component={Feed} />
<Route path="/articles/" exact component={Feed} />
<Route path="/profile/" >
{auth ? <Profile/> : <Redirect to="/" />}
</Route>
<Route path="/settings/" component={Settings} />
<Route path="/write/" >
{auth ? <Write/> : <Redirect to="/" />}
</Route>
<Route path="/search/" component={Search} />
<Route path="/articles/:id" component={ArticleView} />
</Switch>
</>
</Router>
You are making mistake inside requireAuth function. Inside this function then is returning true/false but requireAuth function is not returning anything. So to make requireAuth function return true/false you should return axios value to this function. And also axios returns promises, so to handle promises, it is suggested to use async function
async function requireAuth() {
return await axiosInst.post("http://localhost:3001/checkLogged")
.then(function (response) {
if (response.data.user) {
return true
}else{
return false
}
})
}
And modify your route code to:
....
const [auth, setAuth] = useState(false);
requireAuth().then((res)=>{setAuth(res)})
return(
<Router>
<>
<Navigation/>
<Switch>
<Route path="/login/" exact component={Login} ></Route>
<Route path="/register/" exact component={Register} />
<Route path="/" exact component={Feed} />
<Route path="/articles/" exact component={Feed} />
<Route path="/profile/" >
{auth ? <Profile/> : <Redirect to="/" />}
</Route>
<Route path="/settings/" component={Settings} />
<Route path="/write/" >
{auth ? <Write/> : <Redirect to="/" />}
</Route>
<Route path="/search/" component={Search} />
<Route path="/articles/:id" component={ArticleView} />
</Switch>
</>
</Router>
)
Related
in requestmethods.js
import axios from "axios";
const BASE_URL = "http://localhost:5000/api/";
const TOKEN = JSON.parse(JSON.parse(localStorage.getItem("persist:root"))?.user)
?.currentUser?.accessToken;
export const publicRequest = axios.create({
baseURL: BASE_URL,
});
export const userRequest = axios.create({
baseURL: BASE_URL,
headers: { token: `Bearer ${TOKEN}` },
});
and in app.js
function App() {
const admin = useSelector((state) => state.user.currentUser.isAdmin);
return (
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
{admin && (
<>
<Topbar />
<div className="container">
<Sidebar />
<Route exact path="/">
<Home />
</Route>
<Route path="/users">
<UserList />
</Route>
<Route path="/user/:userId">
<User />
</Route>
<Route path="/newUser">
<NewUser />
</Route>
<Route path="/products">
<ProductList />
</Route>
<Route path="/product/:productId">
<Product />
</Route>
<Route path="/newproduct">
<NewProduct />
</Route>
</div>
</>
)}
</Switch>
</Router>
);
}
I am trying to make if the user is admin show the admin pages, if not show the login page , but when I run npm start I get blank screen , when inspect the page I get these types of error
1=> Uncaught TypeError: state.user.currentUser is null
const admin = useSelector((state) => state.user.currentUser.isAdmin);
Uncaught TypeError: state.user.currentUser is null
This states that the currentUser property in your state.user object is null hence it cannot read the isAdmin property.
One way to avoid this use the safe(elvis) operator '?'.
So use state?.user?.currentUser?.isAdmin instead of state.user.currentUser.isAdmin
Assuming you are using react-router v6, your app.js could look like this
function App() {
const admin = useSelector((state) => state?.user?.currentUser?.isAdmin);
const RequireAuth = () => {
if (!admin) {
return Navigate('/login');
}
return <Outlet/>;
};
return (
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Topbar />
<div className="container">
<Sidebar />
<Route element={<RequireAuth />}>
<Route exact path="/">
<Home />
</Route>
<Route path="/users">
<UserList />
</Route>
<Route path="/user/:userId">
<User />
</Route>
<Route path="/newUser">
<NewUser />
</Route>
<Route path="/products">
<ProductList />
</Route>
<Route path="/product/:productId">
<Product />
</Route>
<Route path="/newproduct">
<NewProduct />
</Route>
</Route>
</div>
</Switch>
</Router>
);
}
You have a good article here
I'm using React Router v6 and am creating private routes for my application.
In file Route.js, I've the code
export default function RouteWrapper({
element: Element,
isPrivate,
...rest
}) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Route {...rest} render={(props) => <Element {...props} />} />;
}
And in file index.js I've written as:
return (
<Routes>
<Route path="/" element={SignIn} />
<Route path="/register" element={SignUp} />
<Route path="/dashboard" element={Dashboard} isPrivate />
<Route path="/profile" element={Profile} isPrivate />
<Route path="/customers" element={Customers} isPrivate />
<Route path="/new" element={New} isPrivate />
<Route path="/new/:id" element={New} isPrivate />
</Routes>
);
}
Is there something I'm missing?
Issue
RouteWrapper isn't a Route component, and fails an invariant check by react-router-dom.
RouteWrapper is directly rendering a Route component, which if the first invariant wasn't failed would trigger another invariant violation. Route components can only be rendered directly by the Routes component or another Route component in the case of building nested routing.
In short, in react-router-dom#6 custom route components are no longer supported. You should instead use wrapper components/layout routes to handle this use case.
Solution
Convert RouteWrapper to a wrapper component that renders an Outlet component for nested routed components to be rendered into.
Example:
import { Navigate, Outlet } from 'react-router-dom';
export default function RouteWrapper({ isPrivate }) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Outlet />; // <-- nested routes render here
}
Wrap the routes you want to protect with the RouteWrapper.
return (
<Routes>
<Route element={<RouteWrapper />}>
<Route path="/" element={<SignIn />} />
<Route path="/register" element={<SignUp />} />
</Route>
<Route element={<RouteWrapper isPrivate />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/customers" element={<Customers />} />
<Route path="/new" element={<New />} />
<Route path="/new/:id" element={<New />} />
</Route>
</Routes>
);
See Layout Routes for further details.
You should convert
<Routes>
<Route exact path="/" element={Dashboard} />
</Routes>
to
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
Also if you want to keep your UI in sync with the URL use like this.
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
</BrowserRouter>
Best.
I am making a login application when i am clicking the login button the i am reciving an error (loginPage.js:33 Uncaught (in promise) TypeError: setLoginUser is not a function)
here is the code of app.js
function App() {
const [user,setLoginUser] = useState({})
return(
<div className='App'>
<BrowserRouter>
< Routes>
<Route path="/homepage" element={user && user?._id? <Homepage /> : <Login setLoginUser={setLoginUser} />}/>
<Route path="/login" element={<Login />} setLoginUser={setLoginUser}/>
<Route path="/register" element={<Register />} />
</Routes>
</BrowserRouter>
</div>
)
}
and here is the code of loginPage.js where error is occuring
const Login = ({ setLoginUser }) => {
const login = () => {
axios.post("http://localhost:4000/login", user)
.then(res => {
alert(res.data.message)
console.log(res.data.user)
setLoginUser(res.data.user)
navigate('/homepage')
})
}
}
You have to pass your setLoginUser function to Login component in your routes.
<Route path="/login" element={<Login setLoginUser={setLoginUser} />} />
You should replace :
<Route path="/login" element={<Login />} setLoginUser={setLoginUser}/>
by
<Route path="/login" element={<Login setLoginUser={setLoginUser}/>} />
I don't think it's a good idea to path variable like that in the root element but this should works.
Why do you have <BrowserRouter> and <Routes> ?
trying to stop a user from accessing my /mainpage route if they arent logged and to redirect them to login.... currently using passport and react router and components... here is my app js
return (
<Router>
<div>
<Route exact path="/" component={Login} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/mainpage" component={MainPage} />
<Footer />
</div>
</Router>
);
}
export default App;
this is the html-routes.js part im trying to make work
app.get("/mainpage", isAuthenticated, (req, res) => {
res.sendFile(path.join(__dirname, "/signup.html"));
});
If you had an endpoint to validate their authentication status, as well as some persisted value (such as isAuthenticated) stored in the UI you can set up an auth gate of sorts.
function AuthGate({ children ]} {
return(
<>
{isAuthenticated && validationCheck() ? children : <Login />}
</>
)
}
and then pass render your protected routes as such
function App() {
return(
<Router>
<div>
<Route exact path="/" component={Login} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
<AuthGate>
<Route exact path="/mainpage" component={MainPage} />
</AuthGate>
<Footer />
</Router>
)
}
this would ensure that as long as your checks to see if a user was authenticated were true, you would render the children of the AuthGate, otherwise return the Login page. Once a user logs in, as long as there is no redirect logic, they will already be on the Mainpage route and view the content as desired. Any routes not wrapped in the AuthGate would be accessible regardless of authentication status.
You can try
app.get('/mainpage', passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' }));
1. declare Private Route and add auth logic
// declare Private Route
type PrivateRouteProps = {
children: React.ReactNode
path: string
exact?: boolean
rest?: never
}
const PrivateRoute: React.FC<PrivateRouteProps> = (
{
children,
path,
exact,
...rest
}: PrivateRouteProps) => {
const store = useGlobalStore()
const loggedIn = store.loggedIn
console.log(store.loggedIn, '??')
return (
<Route
path={path}
exact={exact}
{...rest}
render={(props) =>
loggedIn ? (
children
) : (
<Redirect
to={{
pathname: '/login', // eslint-disable-next-line react/prop-types
state: { from: props.location },
}}
/>
)
}
/>
)
}
2. use Private Router
// use Router
const Router: React.FC = () => (
<BrowserRouter>
<Suspense fallback={<PageLoading />}>
<Switch>
<PrivateRoute path="/" exact>
<Redirect to={'/welcome'} />
</PrivateRoute>
<Route path="/login" exact>
<Login />
</Route>
<Route path="/403" exact>
<Error403 />
</Route>
<Route path="/404" exact>
<Error404 />
</Route>
<Route path="/500" exact>
<Error500 />
</Route>
<PrivateRoute path="*">
<LayoutRoutes />
</PrivateRoute>
</Switch>
</Suspense>
</BrowserRouter>
)
I have the following React component:
function Routes(props) {
const { role, userId } = props;
const renderMemberTasksPage = (props) => {
//Redirects to 404 or not
};
const renderTracksPage = (props) => {
//Redirects to 404 or not
};
return (
<>
<Switch>
<Route exact path='/members/:id/tasks' render={renderMemberTasksPage} />
<Route exact path='/members/:id/tasks/id:open?' render={renderMemberTasksPage} />
{role === 'admin' && (
<Route path='/members/new'>
<NewMember />
</Route>
)}
{(role === 'admin' || role === 'mentor') && (
<>
<Route exact path='/'>
<Redirect to='/members' />
</Route>
<Route exact path='/members'>
<MembersManagerPage />
</Route>
<Route exact path='/tasks/'>
<MemberTasksPage />
</Route>
<Route path='/tasks/new'>
<NewTask />
</Route>
<Route exact path='/tasks/id:open?'>
<MemberTasksPage />
</Route>
<Route path='/tasks/id:open/edit'>
<MemberTasksPage edit />
</Route>
<Route path='/members/:id/progress'>
<MemberProgressPage />
</Route>
</>
)}
{role === 'member' && (
<>
<Route exact path='/'>
<Redirect to={`/members/${userId}/tasks`} />
</Route>
<Route path='/members/:id/tracks' render={renderTracksPage} />
</>
)}
<Route exact path='/404'>
<Error404Page />
</Route>
<Route path='*'>
<Redirect to='/404' />
</Route>
</Switch>
</>
);
}
In simple words, this code defines routes depending on the current user role. Here I've got a problem: default router * is not working. Different order of routes and using exact in different combinations showed no results. When I removed all routes, rendered conditionally, it worked. Can it be the reason and how to avoid such behavior?
My version of react-router-dom:
"react-router-dom": "^5.1.2"
I think you can remove the path='*' and put directly the component inside the <Route>
It would be something like that:
<Route>
<Error404Page />
</Route>
since the Switch will try to match every path the last one will be used if it can't find any