I just learn react and this is the problem I haven't been able to figure out for hours.
I have the App component and I want to pass data about user login to the ProtectedRoute component so I can handle access to the admin page.
In the App there is a function which manages lifting data up from the Login component (based on Firebase user authentication).
The problem is, the userLogged state is set too late so Protected route receives default value of the state.
How can I send the last state to the ProtectedRoute component?
import {useState} from "react"
import './App.css'
import Login from "./components/admin/Login"
import Homepage from "./components/homepage/Homepage"
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import ProtectedRoute from "./ProtectedRoute"
import AdminPage from "./components/admin/AdminPage"
function App() {
const [userLogged, setUserLogged] = useState()
const getUser = (data) => {
setUserLogged(data)
console.log("data: " + data)
console.log("state: " + userLogged)
}
return (
<>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/login" element={<Login sendUser={getUser}/>} />
<Route element={<ProtectedRoute isLoggedIn={userLogged} />}>
<Route path="/admin" element={<AdminPage />} />
</Route>
</Routes>
</>
);
}
export default App
This is the ProtectedRoute component, where I tried to add useEffect. It still doesn't work this way.
import { Navigate, Outlet } from "react-router-dom"
import {useState, useEffect} from "react"
const ProtectedRoute = (props) => {
const [isLogged, setIsLogged] = useState("")
useEffect(() => {
setIsLogged(props.isLoggedIn);
console.log(isLogged);
}, [props.isLoggedIn]);
return isLogged ? <Outlet /> : <Navigate to="/" />;
};
export default ProtectedRoute;
I am trying to redirect from saga after a password change, and history.push() is just updating the URL not loading the component
Hisotry.js
import { createBrowserHistory as history } from "history";
export default history({
bforceRefresh: true,
});
App.js
import React from "react";
import { BrowserRouter } from "react-router-dom";
import history from "../../helpers/history";
import Navigation from "../Navigation";
import AppAuthRouter from "../Navigation/routes";
const App = () => (
<BrowserRouter history={history}>
<Navigation app_auth_router={AppAuthRouter} />
</BrowserRouter>
);
export default App;
AppAuthRouter.js
import * as ROUTES from "../../constants/routes";
import ProtectedRoute from "./protectedroute";
const AppAuthRouter = () => (
<Switch>
<ProtectedRoute path="/admin" component={AdminPage} />
<ProtectedRoute
path="/updatepwd"
component={PasswordChangePage}
/>
<ProtectedRoute path="/welcome" component={WelcomePage} />
<Route path={"/signup} component={SignUpPage} />
<Route path={"/signin} component={SignInPage} />
<Redirect path="*" to={{ pathname:"/signin"}} />
</Switch>
);
export default AppAuthRouter;
Saga.js
import history from "../helpers/history";
function* passwordChangeSaga({ creds }) {
try {
yield put(alertSuccess("Password has been changed successfully"));
history.push("/welcome");
} catch (gerror) {
const error = googleError2Error(gerror);
}
}
Im trying to use React Routing V6 for my project.
Currently im struggeling to make the authentication and routing itself to work.
the idea of my code is:
Not authenticated user:
redirect to /login with my login component. (only login component)
Authenticated user:
Load the gameComponent component, and the rest of links inside of gamecomponent, will load inside gameComponents div class named middleContentHolder
examples:
authenticated user:
visits url /crime -> loads gamecomponent, and within gamecomponent it loads the crime component.
visits url /test -> loads gamecomponent , and within gamecomponent it loads the SideBarRight component.
not authenticated user:
vitits url /crime -> not authenticated -> redirects to /login -> loads loginmodule only.
please note that in gamecomponent component, i do have links that will load within gamecomponent.
app.js will either load the gamecomponent, or redirect user to login if not auth.
app.js:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import GameComponent from './gameComponent.jsx';
import { BrowserRouter as Router } from 'react-router-dom';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
import Crime from './components/game/crime.jsx';
import PrivateRoute from './PrivateRoute';
import Login from './components/login/login.jsx';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<GameComponent />}>
<PrivateRoute isAuth={true} path="crime" component={Crime} redirectTo='/login'/>
</Route>
</Routes>
</Router>
);
}
export default App;
Gamecomponent:
import React, {Component} from 'react';
//import Component from 'react-dom';
import SideBarRight from './components/game/sideBarRight.jsx';
import SideBarLeft from './components/game/sideBarLeft.jsx';
import Crime from './components/game/crime.jsx';
import Login from './components/login/login.jsx';
import './gameComponent.css';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { BrowserRouter} from "react-router-dom";
class GameComponent extends Component{
constructor() {
super();
this.state = {
userData: {
user: {cash:0, bank:0, weapon:'', username: 'test', locationname: 'Bankok',
defence: 0},
rankbar: {rankpercent: 50, rank: 'Mafia'},
}
}
}
render() {
return (
<div className="main">
<div className="sidebar left">
<SideBarLeft/>
</div>
<div className="middleContentHolder">
<Route path="/" element={<Crime />} />
<Route path="/test" element={<Crime />} />
<Route path="/crime" element={<Crime />} />
<Route path="/test" element={<SideBarRight UserData={this.state.userData} />} />
<div className="col-8">
<div className="content">
<div className="header"><span>Test...</span></div>
</div>
</div>
</div>
<div className="sidebar right">
<SideBarRight UserData={this.state.userData}/>
</div>
</div>
);
}
}
export default GameComponent;
PrivateRoute:(auth is just a dummy atm)
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Navigate } from 'react-router-dom';
import { useNavigate } from "react-router-dom";
import Login from './components/login/login.jsx';
import GameComponent from './gameComponent.jsx';
const PrivateRoute = ({ component: Component, redirectTo, isAuth, path, ...props }) => {
isAuth = false;
if(!isAuth) {
return <Navigate to={redirectTo} />;
}
return <Route path={path} element={<Component />} />
};
export default PrivateRoute;
update:
orginal auth was:in privateroute:
isAuth = isAuth;
one example of non-working code that would show what i want:
<Route path="/login" element={}>
<PrivateRoute isAuth={true} path="/" component={GameComponent} redirectTo='/login'>
rest of routes exist in gamecomponent..
</PrivateRoute>
If you only want GameComponent to load if use is authenticated, you will need to change your App component like this:
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<LoginComponent />} />
<PrivateRoute isAuth={true} path="/" component={GameComponent} redirectTo='/login'/>
</Routes>
</Router>
);
}
Here we are essentially putting a switch so that we can navigate to /login when there is no authentication. <Routes> is vital here, because it will only render the component that matches the exact path.
With the official release of React Router V6, the other answer is no longer valid. It will throw an error since <PrivateRoute /> isn't a <Route />.
The proper way to do it is to refactor your <PrivateRoute /> component like so...
import { Navigate, useLocation } from "react-router-dom"
const PrivateRoute = (props: { children: React.ReactNode }): JSX.Element => {
const { children } = props
// Up to you specific app to decide how to get this value
const isLoggedIn: boolean = localStorage.getItem('logged_user') !== null;
const location = useLocation()
return isLoggedIn ? (
<>{children}</>
) : (
<Navigate
replace={true}
to="/login"
state={{ from: `${location.pathname}${location.search}` }}
/>
)
}
Then whichever file you setup your routes in, you would do...
<Routes>
<Route path="/PRIVATE" element={<PrivateRoute> <PrivatePage /> </PrivateRoute>}/>
<Route path="/profile" element={<PrivateRoute> <ProfilePage /> </PrivateRoute>}/>
<Route path="/login" element={<LoginPage />}/>
<Route path="/" element={<HomePage />}/>
</Routes>
This is the proper way of doing it in V6 since only a <Route /> can be nested in a <Routes />. Then your authenticated logic gets moved into the element prop.
As an added bonus, the state={{ from: `${location.pathname}${location.search}` }} in PrivateRoute allows you to get the URL of the page they tried to enter, but was denied. This is passed to your login page, where you can redirect them back to the URL after they authenticate.
Solution for newer version
Create custom middleware <AuthGuard> and use as wrapper
<PrivateRoute> not vaild for newer version as child of <Routes>
Error "A <Route> is only ever to be used as the child of <Routes> element"
App.js
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import AuthGuard from "./Routes/AuthGuard";
function App() {
return (
<div className='App'>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/contact' element={<Contact />} />
<Route path='/guest-page' element={<AuthGuard isGuest={true}><h1>Guest Page</h1></AuthGuard>} />
<Route path='/protected-page' element={<AuthGuard requireToken={true}><h1>ProtectedPage</h1></AuthGuard>} />
</Routes>
</Router>
</div>
);
}
export default App;
AuthGuard.js
import { Route, useNavigate } from "react-router-dom";
import { useLayoutEffect } from "react";
const ProtectedRoute = ({requireToken, guest, children, ...rest}) => {
console.log(requireToken, guest, children, rest);
const navigate = useNavigate();
const hasToken = false;
let hasAccess = (!requireToken || (requireToken && hasToken));
let navigateTo = '/?login-rquired=true';
if(guest) {
hasAccess = !hasToken;
navigateTo = '/?guest=true';
console.log("Guest > hasAccess: " + hasAccess)
}
if(requireToken){
console.log("requireToken", requireToken)
}
useLayoutEffect(()=>{
if (!hasAccess) {
console.log("Not allowed");
navigate(navigateTo);
}
},[])
return (
<>{ hasAccess ? children : "Login required" }</>
)
}
export default ProtectedRoute;
I'm facing a problem with react-router-dom. I'm trying to use history.push for navigating after an action conducted. but the problem is createBrowserHistory from history is updating the urls but components are not re-rendering. I've used every solution from https://stackoverflow.com/. But it's still not working as expected.
However I found a reason behind it. As my components are wrapped with connect function connect is preventing the re-render. And there was a solution too, wrap the connect function with withRouter. I tried it too. But it's not working.
Here is My App.js
import React, { Component } from "react";
import { Router, Route } from "react-router-dom";
import history from "../history"
import Navbar from "./Navbar";
import LogIn from "./LogIn";
import StreamCreate from "./streams/StreamCreate";
import StreamDelete from "./streams/StreamDelete";
import StreamEdit from "./streams/StreamEdit";
import StreamList from "./streams/StreamList";
import StreamShow from "./streams/StreamShow";
import Profile from "./streams/Profile";
class App extends Component {
render() {
return (
<div>
<Router history={history}>
<div>
<Navbar />
<Route path="/" exact component={StreamList} />
<Route path="/streams/new" exact component={StreamCreate} />
<Route path="/streams/delete" exact component={StreamDelete} />
<Route path="/streams/edit" exact component={StreamEdit} />
<Route path="/streams/show" exact component={StreamShow} />
<Route path="/login" exact component={LogIn} />
<Route path="/my-streams" exact component={Profile} />
</div>
</Router>
</div>
);
}
}
export default App;
Here is the history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Action Creator:
import Streams from "../API/Streams";
import history from "../history";
export const createStreams = (formData) => async (dispatch, getState) => {
const { userId } = getState().auth;
const response = await Streams.post("/streams", { ...formData, userId });
dispatch({ type: "CREATE_STREAM", payload: response.data });
history.push("/")
};
There is one need for url authentication:
import React from "react";
import { connect } from "react-redux";
import { Switch, Route, Redirect } from "react-router-dom";
...
const IndexRouter = ({ loggedIn }) => (
<Switch>
<Route
path="/"
render={() => (loggedIn ? <Redirect to="/dashboard" /> : <Login />)}
/>
<Route exact path="/dashboard" component={DashboardRouter} />
<Route exact path="/stock" component={StockRouter} />
</Switch>
);
export default connect(
state => ({
loggedIn: state.persist.loggedIn
}),
{}
)(IndexRouter);
The code means if I have not logged in, all of url are required from client will redirect to Login component. Other than that it will route to DashboardRouter.
The StockRouter is another route related with DashboardRouter.
The problem is that if I logged in. All the unspecific url (except /dashboard, /stock) I manually typed showing the /dashboard url without anything. The specific url such as /stock can show the component StockRouter directly.
You would need to write a PrivateRoute wrapper around your Route and change the order of Routes in IndexRouter, so that the Route with path / is matched at the last otherwise all routes will match / first and will not render correctly
const PrivateRoute = ({component: Component, loggedIn, ...rest }) => {
if(!loggedIn) {
return <Redirect to="/login" />
}
return <Route {...rest} component={Component}/>
}
}
}
const IndexRouter = ({ loggedIn }) => (
<Switch>
<PrivateRoute exact path="/dashboard" component={DashboardRouter} />
<PrivateRoute exact path="/stock" component={StockRouter} />
<Redirect to="/dashboard" />
</Switch>
);
For more details, check Performing Authentication on Routes with react-router-v4
Just create a history component like this :
import React from "react";
import {withRouter} from "react-router";
let globalHistory = null;
class HistoryComponent extends React.Component {
componentWillMount() {
const {history} = this.props;
globalHistory = history;
}
componentWillReceiveProps(nextProps) {
globalHistory = nextProps.history;
}
render() {
return null;
}
}
export const GlobalHistory = withRouter(HistoryComponent);
export default function gotoRoute(route) {
return globalHistory.push(route);
}
And then import into your component:
import gotoRoute from "../../history";
gotoRoute({
pathname: "/your_url_here",
state: {
id: this.state.id
}
});
And in index.js
import {GlobalHistory} from "./history";
ReactDOM.render((
<Provider store={store}>
<BrowserRouter >
<div>
<GlobalHistory/>
<App/>
</div>
</BrowserRouter>
</Provider>
), document.getElementById('root'));