I have a reacj js app using react-routerv5.
Unfortunately am unable to get the this.props.match.params it is empty:
Object { path: "/", url: "/", params: {}, isExact: false }
My App.js is as follows
import React from "react";
//import logo from './logo.svg';
//import './App.css';
import {
Home,
Login,
Register,
NOTFOUND,
Dashboard,
QuestionList,
} from "./components";
import { routeNames } from "./routingParams";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Proute from "./Proute";
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path={routeNames.HOME} exact component={() => <Home />} />
<Route path={routeNames.LOGIN} exact component={() => <Login />} />
<Route
path={routeNames.SIGNUP}
exact
component={() => <Register />}
/>
<Proute
path={routeNames.DASHBOARD}
exact
component={() => <Dashboard />}
/>
<Proute path={"/questions/:test"} component={QuestionList} />
<Route component={NOTFOUND} />
</Switch>
</Router>
</div>
);
}
export default App;
My Proute.js:
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import { AuthService } from "./services";
import { routeNames } from "./routingParams";
import { withRouter } from "react-router";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
class Proute extends Component {
state = {
isAuthenticated: false,
loading: true,
};
componentDidMount() {
var auth = new AuthService();
var _this = this;
auth.isAuthenticated().then(function (result) {
_this.setState({ isAuthenticated: result, loading: false });
});
}
render() {
const Component = this.props.component;
const { isAuthenticated, loading } = this.state;
if (loading) {
return <div>Loading</div>;
}
if (!isAuthenticated) {
return <Redirect to={routeNames.LOGIN} />;
}
return (
<Route>
<Component />
</Route>
);
//const isAuthenticated = true;
}
}
export default Proute;
And my QuestionList:
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import { withRouter } from "react-router";
class QuestionList extends Component {
// Question List page
constructor(props) {
super(props);
}
componentDidMount() {
const id = this.props.match.params.id;
console.log(id);
console.log(this.props.match);
}
render() {
document.title = "Question Lists";
return <div>Question</div>;
}
}
export default withRouter(QuestionList);
In my APP.js instead of using the Proute i use the <Route ..... component am ABLE to get the params. But not if i use the PRoute. I need the Proute since i want to enforce protected rule, but with this am not getting the params.
you should pass the props to the component
EDIT: passed all remaining props instead of just match
in Proute.js:
render() {
const { component: Component, ...rest } = this.props;
const { isAuthenticated, loading } = this.state;
if (loading) {
return <div>Loading</div>;
}
if (!isAuthenticated) {
return <Redirect to={routeNames.LOGIN} />;
}
return (
<Route>
<Component ...rest />
</Route>
);
}
qchmqs's answer should work this way
render() {
const { component: Component, exact, path, ...rest } = this.props;
const { isAuthenticated, loading } = this.state;
if (loading) {
return <div>Loading</div>;
}
if (!isAuthenticated) {
return <Redirect to={routeNames.LOGIN} />;
}
return (
<Route exact={exact} path={path}>
<Component ...rest />
</Route>
);
}
Related
I am making a basic routing app in react and redux and i am getting an error when i want to redirect to the referrer.
Error happens in the line:
this.referrer = props.location.state.referrer || "/";
This is my Login component.
import React, { Component, createRef } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import axios from "axios";
import { updateAuthTokens, updateLoggedin } from "./redux/actions/actions";
class Login extends Component {
constructor(props) {
super(props);
this.idRef = createRef();
this.pwdRef = createRef();
this.state = {
isLoggedIn: false,
isError: false,
identifier: "",
password: "",
};
this.postLogin = this.postLogin.bind(this);
this.referrer = props.location.state.referrer || "/";
}
postLogin(e) {
e.preventDefault();
const identifier = this.idRef.current.value;
const password = this.pwdRef.current.value;
axios
.post(process.env.REACT_APP_BASE_URL + "/auth/local", {
identifier,
password,
})
.then((result) => {
if (result.status === 200) {
this.props.ul(true);
this.props.uat(result.jwt);
this.setState({ isLoggedIn: true });
} else {
this.setState({ isError: true });
}
})
.catch((e) => {
this.setState({ isError: true });
});
}
render() {
if (this.state.isLoggedIn) {
return <Redirect to={this.referrer} />;
} else {
return (
<div>
<form>
<input type="username" ref={this.idRef} placeholder="email" />
<input type="password" ref={this.pwdRef} placeholder="password" />
<button onClick={this.postLogin}>Sign In</button>
</form>
{this.state.isError && (
<p>The username or password provided were incorrect!</p>
)}
</div>
);
}
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
const mapDispatchToProps = {
ul: updateLoggedin,
uat: updateAuthTokens,
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Redirect happens from this component:
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
class PrivateRoute extends React.Component {
render() {
const { isLoggedIn, children, ...rest } = this.props;
if (isLoggedIn) {
return <Route {...rest}>{children}</Route>;
} else {
return (
<Redirect
to={{
pathname: "/login",
state: { referrer: this.props.location }
}}
/>
);
}
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(PrivateRoute);
And my routes are here:
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { connect } from "react-redux";
import Home from "./Home";
import Admin from "./Admin";
import Members from "./Members";
import Login from "./Login";
import PrivateRoute from "./PrivateRoute";
import "./App.css";
class App extends Component {
render() {
return (
<Router><div>
<ul>
<li>
<Link to="/">Home Page</Link>
</li>
<li>
<Link to="/members">Members</Link>
</li>
<li>
<Link to="/admin">Admin</Link>
</li>
</ul>
</div>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route exact path="/">
<Home />
</Route>
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
</Switch>
</Router>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(App);
I haven't been able to get it to work and have had to comment it out which means i always end up in the home page instead of my intended location.
Any help/insight would be appreciated.
for accessing location as props change:
<Route path="/login">
<Login />
</Route>
<Route exact path="/">
<Home />
</Route>
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
to
<Route path="/login" component={Login} />
<Route exact path="/" component={Home} />
<PrivateRoute path="/members" component={Members} />
<PrivateRoute path="/admin" component={Admin} />
You are not going to always redirect so change:
this.referrer = props.location.state.referrer || "/";
to
this.referrer = props.location.state ? props.location.state.referrer : "/";
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import { library } from "#fortawesome/fontawesome-svg-core";
import {
faHome,
faClock,
faTasks,
faStickyNote,
faCalendarWeek
} from "#fortawesome/free-solid-svg-icons";
import { connect } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/authActions";
import Home from "./Home";
import SideNav from "./Components/SideNav";
import Recent from "./Components/Recent";
import TopBar from "./Components/TopBar";
import AddNote from "./AddNote";
import LogIn from "./Components/LogIn/LogIn.js";
import Register from "./Components/Register/Register";
import ToDo from "./Components/ToDo/ToDo";
import { timingSafeEqual } from "crypto";
library.add(faHome, faClock, faTasks, faStickyNote, faCalendarWeek);
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
componentDidUpdate(prevProps) {
if(this.props != this.prevProps) {
console.log("hello")
}
}
LogInContainer = () => {
return <Route path="/login" component={LogIn} />;
};
RegisterContainer = () => {
return <Route path="/register" component={Register} />;
};
DefaultContainer = () => {
return (
<div className="app_container">
<SideNav />
<TopBar />
<Route exact path="/" component={Home} />
<Route path="/recent" component={Recent} />
<Route path="/AddNote" component={AddNote} />
<Route path="/ToDo" component={ToDo} />
</div>
);
};
// Check for authenticaition
AuthRoute = ({ component: Component, props, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (this.props.auth.isAuthenticated) {
return <Component {...props} />;
}
else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
/>
);
}
}}
/>
);
};
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/login" component={this.LogInContainer} />
<Route exact path="/register" component={this.RegisterContainer} />
<this.AuthRoute component={this.DefaultContainer} />
</Switch>
</BrowserRouter>
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth
})
export default connect(mapStateToProps)(App);
How can app.js receive the new state from redux after logging in? The initial fetch it will get isAuthenticated = false. User then log ins but app.js isn't getting the new state. Am I implementing authenitcation wrong? comonentDidUpdate is throwing an error when trying to update props but feel like this is a bad way of doing it anyways
privateRoute.js
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect, withRouter} from "react-router-dom";
import { connect } from "react-redux";
function AuthRoute({ component: Component, isAuth, ...rest }) {
console.log(isAuth);
let test = false;
return (
<Route
{...rest}
render={props =>
isAuth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
const mapStateToProps = state => {
{
console.log(state);
return { isAuth: state.auth.isAuthenticated };
}
};
export default withRouter(connect(mapStateToProps, { pure: false })(AuthRoute));
App.js
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import { library } from "#fortawesome/fontawesome-svg-core";
import PropTypes from "prop-types";
import {
faHome,
faClock,
faTasks,
faStickyNote,
faCalendarWeek
} from "#fortawesome/free-solid-svg-icons";
import { connect } from "react-redux";
import { loadUser, checkAuth } from "./actions/authActions";
import store from "./store";
import Home from "./Home";
import SideNav from "./Components/SideNav";
import Recent from "./Components/Recent";
import TopBar from "./Components/TopBar";
import AddNote from "./AddNote";
import LogIn from "./Components/LogIn/LogIn.js";
import Register from "./Components/Register/Register";
import ToDo from "./Components/ToDo/ToDo";
import AuthRoute from "./privateRoute";
library.add(faHome, faClock, faTasks, faStickyNote, faCalendarWeek);
class App extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false
};
}
static propTypes = {
isAuthenticated: PropTypes.bool
};
componentDidMount() {
store.dispatch(loadUser());
// this.props.isAuthenticated();
}
LogInContainer = () => {
return <Route path="/login" component={LogIn} />;
};
RegisterContainer = () => {
return <Route path="/register" component={Register} />;
};
DefaultContainer = () => {
return (
<div className="app_container">
<SideNav />
<TopBar />
<Route exact path="/" component={Home} />
<Route path="/recent" component={Recent} />
<Route path="/AddNote" component={AddNote} />
<Route path="/ToDo" component={ToDo} />
</div>
);
};
render() {
return (
<div>
<h1> {this.props.auth.isAuthenticated.toString()}</h1>
<BrowserRouter>
<Switch>
<Route exact path="/login" component={this.LogInContainer} />
<Route exact path="/register" component={this.RegisterContainer} />
<AuthRoute component={this.DefaultContainer} />
</Switch>
</BrowserRouter>
</div>
);
}
}
const mapStateToProps = state => {
return { auth: state.auth };
};
export default connect(mapStateToProps)(App);
When the user logins, an action will be dispatched to authentication the user. In redux dev tools, I can see the user authenticated.
Within privateRoute.js, mapStateToProps is always matching the initial state and not the updated state so isAuth never equates to true and I can't show the protected route?
I've been stuck on this for days, I feel like I'm implementing this wrong however looking at other examples I can't see what I am doing wrong.
The issue is how you are mapping state to props. It looks like your store state is structured as { auth: { isAuthenticated: boolean } }. However in the private route map state to props, you are trying to access effectively auth.isAuthenticated.isAuthenticated which will always be false/undefined. You need to go level higher in your state structure when mapping state to props. Try updating the private route component to:
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect, withRouter } from "react-router-dom";
import { connect } from "react-redux";
function AuthRoute({ component: Component, isAuth, ...rest }) {
console.log(isAuth);
let test = false;
return (
<Route
{...rest}
render={props =>
isAuth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
const mapStateToProps = state => {
{
console.log(state);
return { isAuth: state.auth }; // change this line
}
};
export default withRouter(connect(mapStateToProps)(AuthRoute));
I have an application with multi user login functionality. Now, I created the redux store for the login function. If User logged in, based on their type, it will redirect to the particular dashboard. If student user logged in, he should be able to only access student dashboard, he should not able to access other dashboards. Same applies for others. But, now, if any user logged in, they can able to access other user dashboards. How can i prevent them to use only their dashboards.
Updated With Proper Code
/***AppRouter***/
import React, {Component} from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import StuDashboard from '../views/ed/dashboard/Dashboard.js';
import AdminDashboard from '../views/biz/dashboard/Dashboard.js';
import OrgDashboard from '../views/org/dashboard/Dashboard.js';
import SupAdDashboard from '../views/me/dashboard/Dashboard.js';
import UserLogin from '../views/loginUser/Login.js';
import history from '../history/history.js';
import { PrivateRoute } from './PrivateRoute.js';
import NotFoundPage from '../views/NotFoundPage.js';
import Authorization from './Authorization';
class AppRouter extends Component {
render () {
return(
<BrowserRouter history={history}>
<Switch>
<Route path="/" component={Landing} exact />
<Route path="/confirmation" component={EmailCon}/>
<PrivateRoute path="/student/dashboard" component={Authorization(StuDashboard),["Student"]}/>
<PrivateRoute path="/admin/dashboard" component={Authorization(AdminDashboard),["Admin"}/>
<PrivateRoute path="/org/dashboard" component={Authorization(OrgDashboard),["Org"]}/>
<PrivateRoute path="/SuperAdmin/dashboard" component={Authorization(SupAdDashboard),["SuperAdmin"]}/>
<Route path="/login" component={UserLogin}/>
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>
);
}
}
export default AppRouter;
/***PrivateRoute***/
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('token')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
/***Authorization***/
import React, { Component } from 'react';
import { connect } from "react-redux";
const Authorization = (WrappedComponent, allowedRoles) =>{
class WithAuthorization extends Component {
render() {
const userType = this.props.user
if (allowedRoles.includes(userType)) {
return <WrappedComponent {...this.props} />
} else {
return <h1>You are not allowed to view this page!</h1>
}
}
}
const mapStateToProps = state => ({ user: state.login.userName, userType: state.login.userType })
return connect(mapStateToProps)(WithAuthorization);
}
export default Authorization;
/***Action.js***/
import axios from "axios";
import { LOGIN_PENDING, LOGIN_COMPLETED, LOGIN_ERROR, LOGOUT } from "./types";
import ApiUrl from "../../constants/ApiUrl.js";
import qs from "qs";
import history from '../../history/history.js';
const startLogin = () => {
return {
type: LOGIN_PENDING
};
};
const loginComplete = data => ({
type: LOGIN_COMPLETED,
data
});
const loginError = err => ({
type: LOGIN_ERROR,
err
});
export const loginUser = (email, password) => {
return dispatch => {
dispatch(startLogin());
let headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
const data = qs.stringify({
grant_type: "password",
username: email,
password: password,
});
axios
.post(`${ApiUrl}/Token`, data, {
headers: headers
})
.then(function (resp) {
dispatch(loginComplete(resp.data));
localStorage.setItem("token", resp.data.access_token);
switch (resp.data.userType) {
case 'Admin': {
history.push('/admin/dashboard');
break;
}
case 'Student': {
history.push('/student/dashboard');
break;
}
case 'Organization': {
history.push('/org/dashboard');
break;
}
case 'SuperAdmin': {
history.push('/SuperAdmin/dashboard');
break;
}
default:
history.push('/');
}
window.location.reload();
return;
})
.catch(err => dispatch(loginError(err)));
};
};
export const logOut = () => {
localStorage.clear();
return {
type: LOGOUT,
};
}
I added an Authorization HOC to cater for access control. So you just pass the component, and the allowed role to the Authorization HOC.First i assume your user has a property userType. check out this URL.
/***Authorization***/
import React, { Component } from "react";
import { connect } from "react-redux";
const Authorization = (WrappedComponent, allowedRoles) => {
class WithAuthorization extends Component {
render() {
const userType = this.props.userType;
if (allowedRoles.includes(userType)) {
return <WrappedComponent {...this.props} />;
} else {
return <h1>You are not allowed to view this page!</h1>;
}
}
}
const mapStateToProps = state => ({ user: state.login.userName, userType: state.login.userType })
return connect(mapStateToProps)(WithAuthorization);
};
export default Authorization;
your router will then look this way
<BrowserRouter history={history}>
<Switch>
<Route path="/" component={Landing} exact />
<Route path="/confirmation" component={EmailCon}/>
<PrivateRoute path="/student/dashboard" component={Authorization(StuDashboard,["Student"])}/>
<PrivateRoute path="/admin/dashboard" component={Authorization(AdminDashboard,["Admin"])}/>
<PrivateRoute path="/org/dashboard" component={ Authorization(OrgDashboard,["Organization"])}/>
<PrivateRoute path="/SuperAdmin/dashboard" component={Authorization(SupAdDashboard,["SuperAdmin"])}/>
<Route path="/login" component={UserLogin}/>
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>
I have following routing configuration:
return (<div>
<Router>
<div>
<Route path='/login' component={LoginPage}/>
<EnsureLoggedInContainer>
<Route path='/abc' component={abc} />
</EnsureLoggedInContainer>
</div>
</Router>
</div>
);
The EnsureLoggedInContainer is:
import React from 'react';
import { connect } from "react-redux";
class EnsureLoggedInContainer extends React.Component
{
componentDidMount() {
if ( !this.props.isLoggedIn )
{
// this.props.history.push('/login');
this.context.router.push('/contact');
}
}
render() {
// console.log(this.props);
if ( this.props.isLoggedIn )
{
return this.props.children;
}
else
{
return null;
}
}
}
const mapStateToProps = (state,ownProps) => {
return{
isLoggedIn : state.isLoggedIn,
// currentURL : this.props
}
}
export default connect(mapStateToProps)(EnsureLoggedInContainer);
But, the history push: this.props.history.push('/login'); isn't working. Here history is not present.
If I am using a configuration like this:
<Route component={EnsureLoggedInContainer}>
<Route path='/myjs' component={MyjsPage} />
</Route>
I am getting issue like:
Warning: You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
What's the best way of authentication in reactjs?
From what I can see of your React Router Design, you seem to be using React router version 4
In that case you can specify the route in the Component Itself, and make use of withRouter to do a dynamic redirect like
return (<div>
<Router>
<div>
<Route path='/login' component={LoginPage}/>
<EnsureLoggedInContainer/>
</div>
</Router>
</div>
);
and
import React from 'react';
import { connect } from "react-redux";
import {withRouter} from "react-router";
class EnsureLoggedInContainer extends React.Component
{
componentDidMount() {
if ( !this.props.isLoggedIn )
{
this.props.history.push('/login');
}
}
render() {
// console.log(this.props);
if ( this.props.isLoggedIn )
{
return <Route path='/abc' component={abc} />
}
else
{
return null;
}
}
}
const mapStateToProps = (state,ownProps) => {
return{
isLoggedIn : state.isLoggedIn,
// currentURL : this.props
}
}
export default connect(mapStateToProps)(withRouter(EnsureLoggedInContainer));