Props passed through React Route cant be accessed by child component - javascript

I am having a problem accessing the props passed through the route to the child component.
I am trying to get hold of the Authentication function in the App page , so that I can toggle it to true when my onLogin function in the Login page get the correct response.
Any help will be highly appreciated.
please find the code below
//App.js
import React, { Component } from "react";
//import TopNavigation from './components/topNavigation';
//import { BrowserRouter, Route, Switch } from "react-router-dom";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import MainPage from "./Mainpage";
import LoginPage from "./components/pages/LoginPage";
//import ProtectedRoute from "./protected.route";
import "./styleFiles/index.css";
class App extends Component {
constructor() {
super();
this.state = {
isAuthenticated: false
};
this.Authentication = this.Authentication.bind(this);
}
Authentication(e) {
this.setState({ isAuthenticated: e });
}
render() {
if (this.state.isAuthenticated === false) {
return (
<BrowserRouter>
<div className="flexible-content ">
<Route
path="/login"
render={props => <LoginPage test="helloworld" {...props} />}
/>
<Route component={LoginPage} />
</div>
</BrowserRouter>
);
} else {
return (
<div className="flexible-content ">
<Switch>
<Route
path="/"
exact
component={MainPage}
/>
<Route component={MainPage} />
</Switch>
</div>
);
}
}
}
export default App;
//login page
import React, { Component } from "react";
import logo from "../../assets/justLogo.svg";
import "../../styleFiles/loginCss.css";
class LoginPage extends Component {
constructor(props) {
super(props);
}
onLogin = event => {
let reqBody = {
email: "rpser1234#gmail.com",
password: "rpser1234"
};
// event.preventDefault();
fetch("http://localhost:8000/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(reqBody)
})
.then(res => {
if (res.ok) {
return res.json();
} else {
throw new Error("Something went wrong with your fetch");
}
})
.then(json => {
localStorage.setItem("jwtToken", json.token);
console.log(this.props.test);
});
};
render() {
const {
test,
match: { params }
} = this.props;
console.log(test);
return (
<div className="bodybg">
<div className="login-form">
<div className="form-header">
<div className="user-logo">
<img alt="MDB React Logo" className="img-fluid" src={logo} />
</div>
<div className="title">Login</div>
</div>
<div className="form-container">
<div className="form-element">
<label className="fa fa-user" />
<input type="text" id="login-username" placeholder="Username" />
</div>
<div className="form-element">
<label className="fa fa-key" />
<input type="text" id="login-password" placeholder="Password" />
</div>
<div className="form-element">
<button onClick={this.onVerify}>verify</button>
</div>
<div className="form-element forgot-link">
Forgot password?
</div>
<div className="form-element">
<button onClick={this.onLogin}>login</button>
</div>
</div>
</div>
</div>
);
}
}
export default LoginPage;
I am trying to access the test prop from the loginPage but no success.
Any idea were am I going wrong?

Try this:
<Route
path="/login"
render={props => <LoginPage test={this.state.isAuthenticated} {...props} />}
/>
You have isAuthenticated not Authentication.

You are using state to pass function as prop,
<Route
path="/login"
render={props => <LoginPage test={this.state.Authentication} {...props} />} //Wrong way to pass function as prop
/>
If you want to pass a function as prop use this, Ref
<Route
path="/login"
render={props => <LoginPage test={this.Authentication} {...props} />}
/>
If you want multiple routes for same component, then use exact attribute like this, check this
<Route
exact //This will match exact path i.e. '/login'
path="/login"
render={props => <LoginPage test={this.Authentication} {...props} />}
/>

Your approach is not the best practice. For such purposes it is best to use redux + redux-thunk.
If you still want to go this way.
You make mistake on here:
<Route
path="/login"
render={props => <LoginPage test={this.state.isAuthention} {...props} />}
/>
this.state.isAuthention replace on this.state.isAuthenticated
After that you need send via props Authentication callback and call it's in fetch( ... ).then((result) => this.props.Authentication(result))

Related

React.js - Functions are not valid as a React child

I am new to React.js. I can't solve the problem. I am getting this warning:
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
App.js
`
import React from 'react';
import MovieList from './MovieList';
import SearchBar from './SearchBar';
import AddMovie from './AddMovie';
import axios from 'axios'
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
class App extends React.Component {
state = {
movies: [],
searchQuery: ""
}
async componentDidMount() {
const response = await axios.get("http://localhost:3002/movies")
this.setState({movies: response.data})
}
deleteMovie = async (movie) => {
axios.delete(`http://localhost:3002/movies/${movie.id}`)
const newMovieList = this.state.movies.filter(
m => m.id !== movie.id
)
this.setState(state => ({
movies: newMovieList
}))
}
searchMovie = (event) => {
this.setState({searchQuery: event.target.value })
}
render() {
let filteredMovies = this.state.movies.filter(
(movie) => {
return movie.name.toLowerCase().indexOf(this.state.searchQuery.toLowerCase()) !== -1
}
)
return (
<Router>
<div className="container">
<Routes>
<Route path='/' exact element={() =>(
<React.Fragment>
<div className="row">
<div className="col-lg-12">
<SearchBar const searchMovieProp={this.searchMovie()} />
</div>
</div>
<MovieList
movies={filteredMovies()}
deleteMovieProp={this.deleteMovie()}
/>
</React.Fragment>
)}>
</Route>
<Route path='/add' element={<AddMovie />} />
</Routes>
</div>
</Router>
)
}
}
export default App;
`
What am I doing wrong?
Thanks in advance.
Passing a function to a route like you did:
<Route path='/' exact element={() =>(
<React.Fragment>
<div className="row">
<div className="col-lg-12">
<SearchBar const searchMovieProp={this.searchMovie()} />
</div>
</div>
<MovieList
movies={filteredMovies()}
deleteMovieProp={this.deleteMovie()}
/>
</React.Fragment>
)}>
looks like a router v5 syntax. This is not working in v6: you should pass an element, which is different than a function producing an element. Something like this would work:
<Route path='/' exact element={(
<React.Fragment>
<div className="row">
<div className="col-lg-12">
<SearchBar const searchMovieProp={this.searchMovie()} />
</div>
</div>
<MovieList
movies={filteredMovies()}
deleteMovieProp={this.deleteMovie()}
/>
</React.Fragment>
)}>

this.props.history.push() is not working when i pass the callback function in same route

I have
<Route path='/login' exact render={() => <LoginPage sectionEmailHandler={this.sectionEmailHandler} />} />
this Route with path /login ,and i am passing callback function
sectionEmailHandler
and inside login component i am doing
this.props.history.push('/dashboard/session')
so now i am ,getting the error that
TypeError: Cannot read property 'push' of undefined
but when i did not pass the callback function with /login route like this
<Route path='/login' exact component={LoginPage} />
then it works fine.
Below is my code
Pages/index.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import LoginPage from '../components/Login/LoginPage'
import StartPage from '../components/startPage/StartHere'
import users from '../pages/users/index'
class Home extends Component {
// constructor(props){
// super(props)
// this.state={
// email:''
// }
// }
sectionEmailHandler=(email)=>{
console.log(email)
}
render() {
return (
<>
<Router >
<Switch>
<Route path='/' exact component={StartPage} />
<Route path='/login' exact render={() => <LoginPage sectionEmailHandler={this.sectionEmailHandler} />} />
<Route path='/dashboard/session' exact component={users} />
</Switch>
</Router>
</>
)
}
}
export default Home;
LoginPage.js
import React, { Component } from 'react'
import { Redirect } from 'react-router-dom';
import { Button,Label } from 'reactstrap';
import {Mutation, Query} from 'react-apollo';
import gql from 'graphql-tag'
import LoginPage_logo_img from '../../Assets/LoginPage_logo_img.png'
import LoginPage_email_icon from '../../Assets/LoginPage_email_icon.svg'
import LoginPage_lock_icon from '../../Assets/LoginPage_lock_icon.svg'
import './LoginPage.css'
const AUTH_USER = gql`
mutation TokenAuth($username: String!,$password : String!) {
tokenAuth(username: $username,password : $password) {
token,
payload,
refreshExpiresIn
}
}
`;
class LoginPage extends Component {
constructor(props){
super(props)
this.state={
fields: {},
errors: {},
email:'',
password:''
}
this.textInputemail = React.createRef();
this.textInputpassword = React.createRef();
}
handleValidation(){
let fields = this.state.fields;
let errors = {};
let formIsValid = true;
//Email
if(!fields["email"]){
formIsValid = false;
errors["email"] = "Please complete the field above";
}
if(typeof fields["email"] !== "undefined"){
let lastAtPos = fields["email"].lastIndexOf('#');
let lastDotPos = fields["email"].lastIndexOf('.');
if (!(lastAtPos < lastDotPos && lastAtPos > 0 && fields["email"].indexOf('##') == -1 && lastDotPos > 2 && (fields["email"].length - lastDotPos) > 2)) {
formIsValid = false;
errors["email"] = "Please enter the valid Email";
}
}
this.setState({errors: errors});
return formIsValid;
}
handleChnage=()=>{
let fields = this.state.fields;
fields[this.textInputpassword.current.name] = this.textInputpassword.current.value;
fields[this.textInputemail.current.name] = this.textInputemail.current.value;
this.setState({fields});
}
render() {
return (
<Mutation mutation={AUTH_USER}>
{(authUser,{data})=>(
<div className="LoginPage-container">
<div className="LoginPage-wrapper">
<img src={LoginPage_logo_img} alt="" className="LoginPage-logo"/>
<div className="LoginPage-email">
<img src={LoginPage_email_icon} alt=""/>
<input ref={this.textInputemail} type="email" name="email" onChange={this.handleChnage}/>
</div>
<div className="LoginPage-password">
<img src={LoginPage_lock_icon} alt=""/>
<input ref={this.textInputpassword} type="password" name="password" placeholder="Password" onChange={this.handleChnage}/>
</div>
<Button className="LoginPage-signIn" onClick={(e)=>{
e.preventDefault()
const {email,password}= this.state.fields;
authUser({
variables:{
username:email,
password:password
}
}).then((res)=>{
let token=res.data.tokenAuth.token;
localStorage.setItem('WonEToken', token);
this.props.sectionEmailHandler(email)
console.log(this.props)
this.props.router.push('/dashboard/session');
}).catch((err)=>{
console.log(err+'Error while fetching the user details');
})
}}>Sign In</Button>
<h2></h2>
</div>
</div>
)}
</Mutation>
)
}
}
export default LoginPage
Pass the route props from the Route on to the rendered component.
<Route
path='/login'
exact
render={(routeProps) => ( // <-- { history, location, match }
<LoginPage
sectionEmailHandler={this.sectionEmailHandler}
{...routeProps} // <-- spread in to component
/>
)}
/>
I assume you are using react router.
In your example
<Route path='/login' exact render={() => <LoginPage sectionEmailHandler={this.sectionEmailHandler} />} />
You need to pass in the props like this.
<Route path='/login' exact render={(props) => <LoginPage sectionEmailHandler={this.sectionEmailHandler} {...props} />} />
The props contains match, location, history
The children render prop receives all the same route props as the
component and render methods, except when a route fails to match the
URL, then match is null.

How can I pass in props from App.js to my login component?

I'm new to React. I'm having trouble with getting React to recognize a prop passed in from App.js to the Login component. Specifically what I'm trying to do is getting React to do two things: 1. To check if a user is logged in. 2. What user is currently logged in. I'm using Passport, passport-local, Mongoose, MongoDB, Axios, Express, and react-router-dom to make this happen. When I attempt to log in a user I created in the database I get the following in the console:
TypeError: "this.props.updateUser is not a function"
onSubmit login.component.js:42
App.js:
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import axios from "axios";
/* Begin Import Components */
import NavigationBar from "./components/navbar.component";
import MainLanding from "./components/main-landing.component";
import Contact from "./components/general-components/contact.component";
import Register from "./components/general-components/register.component";
import Login from "./components/general-components/login.component";
import ProductsList from "./components/product-components/products-list.component";
import ProductDetails from "./components/product-components/product-details.component";
import ProductCreate from "./components/product-components/product-create.component";
import ProductEdit from "./components/product-components/product-edit.component";
import ProductDelete from "./components/product-components/product-delete.component";
import UsersList from "./components/user-components/users-list.component";
import UserDetails from "./components/user-components/user-details.component";
import OrdersList from "./components/order-components/orders-list.component";
import OrderDetails from "./components/order-components/order-details.component";
import OrderCreate from "./components/order-components/order-create.component";
import OrderEdit from "./components/order-components/order-edit.component";
/* End Import Components */
class App extends Component {
constructor(props) {
super(props);
this.state = {
loggedIn: false,
user: {}
}
this.getUser = this.getUser.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.updateUser = this.updateUser.bind(this)
}
getUser() {
axios.get('http://localhost:4000/logged_in').then(response => {
console.log('Get user response: ')
console.log(response.data)
if (response.data.user) {
this.setState({
loggedIn: true,
user: response.data.user
})
} else {
console.log('Get user: no user');
this.setState({
loggedIn: false,
user: {}
})
}
})
}
componentDidMount() {
this.getUser();
}
updateUser(userObject) {
this.setState(userObject)
}
render() {
return (
<Router>
<NavigationBar />
<div className="container">
{/* Begin Routes. Begin General Routes */}
<Route path="/" exact component={MainLanding} />
<Route path="/contact" exact component={Contact} />
<Route path="/register" exact component={Register} />
<Route path='/login' render={(props) => <Login {...props} />}/>
{/* End General Routes. Begin Products Routes */}
<Switch>
<Route path="/products/" exact component={ProductsList} />
<Route path="/products/new" exact component={ProductCreate} />
<Route path="/products/:id" exact component={ProductDetails} />
<Route path="/products/:id/edit" exact component={ProductEdit} />
<Route path="/products/:id/delete" exact component={ProductDelete} />
</Switch>
{/* End Products Routes. Begin Users Routes */}
<Switch>
<Route path="/users/" exact component={UsersList} />
<Route path="/users/:id" exact component={UserDetails} />
</Switch>
{/* End Users Routes. Begin Orders Routes */}
<Switch>
<Route path="/orders/" exact component={OrdersList} />
<Route path="/orders/new" exact component={OrderCreate} />
<Route path="/orders/:id" exact component={OrderDetails} />
<Route path="/orders/:id/edit" exact component={OrderEdit} />
</Switch>
{/* End Orders Routes. End Routes */}
</div>
</Router>
);
}
}
export default App;
Login Component:
import React, { Component } from "react";
import { Col, Form, Button } from "react-bootstrap";
import axios from "axios";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
}
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onchangePassword = this.onchangePassword.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChangeUsername(e) {
this.setState({
username: e.target.value
});
}
onchangePassword(e) {
this.setState({
password: e.target.value
});
}
onSubmit(e) {
e.preventDefault();
axios.post("http://localhost:4000/login", {
username: this.state.username,
password: this.state.password
}).then(res => {
console.log(res)
if (res.status === 200) {
this.props.updateUser({
loggedIn: true,
username: res.data.username
})
}
}).catch(error => {
console.log(error);
});
this.setState({
username: "",
password: ""
})
this.props.history.push("/");
}
render() {
return (
<div className="text-center">
<h2>Login</h2>
<Form onSubmit={this.onSubmit}>
<Form.Row>
<Form.Group as={Col} sm={{ span: 6 }}>
<Form.Label htmlFor="formUsername">Username</Form.Label>
<Form.Control
controlid="formUsername"
type="text"
value={this.state.username}
onChange={this.onChangeUsername}
placeholder="Enter username"
/>
</Form.Group>
<Form.Group as={Col} sm={{ span: 6 }}>
<Form.Label htmlFor="formPassword">Password</Form.Label>
<Form.Control
controlid="formPassword"
type="password"
value={this.state.password}
onChange={this.onchangePassword}
placeholder="Enter password"
/>
</Form.Group>
</Form.Row>
<Button variant="success" type="submit">
Login
</Button>
</Form>
</div>
)
}
}
export default Login;
Let me know if I need to provide any additional information. Thank you.
The function updateUser() is defined correctly but you need to pass it to the Login component:
App.js
<Route path='/login' render={(props) =>
<Login {...props} updateUser={this.updateUser} />
}/>
This will append props.updateUser to the Login component on top of all the props from the parent component (App.js).
It does seem to be that you still not put updateUser as props of Login component. You could try like this
<Route path='/login' render={(props) =>
<Login {...props} updateUser={this.updateUser} />
}/>
But, i think that this.props.updateUser() may also be come from redux of application (mapDispatchtoProps). If so, you just connect Login component to redux, don't need to put this function as props in Login component.

React Router: state variable updated with setState not being passed down to Route correctly

So, I have the following problem:
I want to allow users to set the language of my portfolio. To do so, I provide them with two links in the initial <Language /> component, which set the state of <App /> and then lead the user to the home screen - <Home />. The problem is, the updated this.state.language is not passed down to <Home />; instead, the initial value it had is passed down.
My code:
// <App /> component
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
language: "none"
}
}
changeLanguage = event => {
event.preventDefault()
this.setState({ language: event.target.id }, () => {
window.location.href = "/home"
})
}
render() {
return (
<BrowserRouter>
<div id="app">
<Route
exact
path="/"
render={() => (
<Language changeLanguage={this.changeLanguage} />
)}
/>
<Route
path="/home"
component={() => {
return <Home language={this.state.language} />
}}
/>
<Route
path="/about"
render={() => {
return <About language={this.state.language} />
}}
/>
<Route
path="/projects"
render={() => {
return <Projects language={this.state.language} />
}}
/>
<Route
path="/contact"
render={() => {
return <Contact language={this.state.language} />
}}
/>
</div>
</BrowserRouter>
)
}
}
// <Language /> component
const Language = props => {
return (
<div id="language">
<h1>Choose your language.</h1>
<div className="lang-options">
<a
href="/home"
className="lang-link"
id="pt_BR"
onClick={props.changeLanguage}
title="Português Brasileiro"
>
<img
src="https://s3-sa-east-1.amazonaws.com/myhostedfiles.raulf/Images/svg-icons/brazil-flag.svg"
alt="A bandeira brasileira. Clique para ter acesso ao site em português."
/>
</a>
<a
href="/home"
className="lang-link"
id="en_US"
onClick={props.changeLanguage}
title="American English"
>
<img
src="https://s3-sa-east-1.amazonaws.com/myhostedfiles.raulf/Images/svg-icons/usa-flag.svg"
alt="The american flag. Click to access the site in english."
/>
</a>
</div>
<h1>Escolha seu idioma.</h1>
</div>
)
}
// <Home /> component
const Home = (props) => {
console.log(props.language)
return (
<div id="home">
<div className="bg-filter" />
<Navbar />
<TypedIntro />
<LinkBox />
</div>
)
}
When the <Home /> component is loaded, console.log(props.language) logs none to the console, which is the initial value this.state.language is set to. Can anybody explain to me why won't it update, and how to fix it?
Setting window.location.href is going to refresh the page. That will lose all your state. Change your <a> tags to use the react-router <Link to="/home"> tag instead.
Here's a CodeSandbox to help illustrate what is going on:
Have you tried exploring this as a solution? You would have a file where you define your keys to each language and pass around an i18n object to access each key. This is an anti-pattern as you are trying to mutate state with multiple components.
You were having issues because you were using windows.location.href it will refresh the app. And after that you will get a new instance of the app and you will lose the state of the app. And it was the reason you were getting initial state in console.
Change windows.location.href to this.props.history.push() but to do so you need to wrap App component with withRouter HOC from 'react-router-dom'. I have changed your code in some places check that out. And try to use Link tag instead of a tag.
import { BrowserRouter , Route, withRouter} from "react-router-dom";
.....
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
language: "none"
}
}
changeLanguage = event => {
event.preventDefault()
// react-router passes "history" as props
this.setState({ language: event.target.id }, () => this.props.history.push('/home'))
}
render() {
return (
<div id="app">
<Route
exact
path="/"
render={() => (
<Language changeLanguage={this.changeLanguage} />
)}
/>
<Route
path="/home"
component={() => {
return <Home language={this.state.language} />
}}
/>
</div>
)
}
}
// wrapping App component with "withRouter" HOC
const RouterApp = withRouter(App)
// <NewApp /> Component
// you need to do this because component wrapped inside
// "withRouter" HOC must be inside "Router" component
const NewApp = () => {
return <BrowserRouter>
<RouterApp />
</BrowserRouter>
}
// <Language/> Component
const Language = props => {
return (
<div id="language">
<h1>Choose your language.</h1>
<div className="lang-options">
<p
className="lang-link"
id="pt_BR"
onClick={props.changeLanguage}
title="Português Brasileiro"
>
Português
</p>
<p
className="lang-link"
id="en_US"
onClick={props.changeLanguage}
title="American English"
>
American
</p>
</div>
<h1>Escolha seu idioma.</h1>
</div>
)
}
// <Home Component/>
const Home = (props) => {
console.log(props.language)
return (
<div id="home">
Home Component
<p>Language: {props.language}</p>
</div>
)
}
// render NewApp component
ReactDOM.render(<NewApp />, document.getElementById('root'));
You should use <Link to='/home'> instead of window.location.href = "/home".
This will reload the page and reload the component. Also it disables react's capability as a SPA.

How to implement authenticated routes in React Router 4?

I was trying to implement authenticated routes but found that React Router 4 now prevents this from working:
<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
<Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
<Route exact path="/domains" component={DomainsIndex} />
</Route>
The error is:
Warning: You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
In that case, what's the correct way to implement this?
It appears in react-router (v4) docs, it suggests something like
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
But is it possible to achieve this while grouping a bunch of routes together?
After some research, I came up with this:
import React, {PropTypes} from "react"
import {Route} from "react-router-dom"
export default class AuthenticatedRoute extends React.Component {
render() {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <Route {...this.props} />
}
}
AuthenticatedRoute.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
component: PropTypes.element,
redirectToLogin: PropTypes.func.isRequired
}
Is it correct to dispatch an action in render()? It feels wrong. It doesn't really seem correct with componentDidMount or some other hook, either.
You're going to want to use the Redirect component. There's a few different approaches to this problem. Here's one I like, have a PrivateRoute component that takes in an authed prop and then renders based on that props.
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
Now your Routes can look something like this
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
If you're still confused, I wrote this post that may help -
Protected routes and authentication with React Router v4
All answers are outdated
In 2022 the render prop of the Route component is for legacy use according to the react-router-dom documentation is not even working anymore in V5 and in V6 was removed.
This works instead:
const RequireAuth: FC<{ children: React.ReactElement }> = ({ children }) => {
const userIsLogged = useLoginStatus(); // Your hook to get login status
if (!userIsLogged) {
return <LoginPage />;
}
return children;
};
Usage:
/* A route that doesn't require login */
<Route
path="sign-up"
element={
<SignUpPage />
}
/>
/* A route that requires login */
<Route
path="dashboard"
element={
<RequireAuth>
<DashboardPage />
</RequireAuth>
}
/>
EDIT: I updated the code example to v6 of React Router
Tnx Tyler McGinnis for solution.
I make my idea from Tyler McGinnis idea.
const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
return (
<Route
{...rest}
render={
decisionFunc()
? trueComponent
: falseComponent
}
/>
)
}
You can implement that like this
<DecisionRoute path="/signin" exact={true}
trueComponent={redirectStart}
falseComponent={SignInPage}
decisionFunc={isAuth}
/>
decisionFunc just a function that return true or false
const redirectStart = props => <Redirect to="/orders" />
(Using Redux for state management)
If user try to access any url, first i am going to check if access token available, if not redirect to login page,
Once user logs in using login page, we do store that in localstorage as well as in our redux state. (localstorage or cookies..we keep this topic out of context for now).
since redux state as updated and privateroutes will be rerendered. now we do have access token so we gonna redirect to home page.
Store the decoded authorization payload data as well in redux state and pass it to react context. (We dont have to use context but to access authorization in any of our nested child components it makes easy to access from context instead connecting each and every child component to redux)..
All the routes that don't need special roles can be accessed directly after login.. If it need role like admin (we made a protected route which checks whether he had desired role if not redirects to unauthorized component)
similarly in any of your component if you have to disable button or something based on role.
simply you can do in this way
const authorization = useContext(AuthContext);
const [hasAdminRole] = checkAuth({authorization, roleType:"admin"});
const [hasLeadRole] = checkAuth({authorization, roleType:"lead"});
<Button disable={!hasAdminRole} />Admin can access</Button>
<Button disable={!hasLeadRole || !hasAdminRole} />admin or lead can access</Button>
So what if user try to insert dummy token in localstorage. As we do have access token, we will redirect to home component. My home component will make rest call to grab data, since jwt token was dummy, rest call will return unauthorized user. So i do call logout (which will clear localstorage and redirect to login page again).
If home page has static data and not making any api calls(then you should have token-verify api call in the backend so that you can check if token is REAL before loading home page)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import history from './utils/history';
import Store from './statemanagement/store/configureStore';
import Privateroutes from './Privateroutes';
import Logout from './components/auth/Logout';
ReactDOM.render(
<Store>
<Router history={history}>
<Switch>
<Route path="/logout" exact component={Logout} />
<Route path="/" exact component={Privateroutes} />
<Route path="/:someParam" component={Privateroutes} />
</Switch>
</Router>
</Store>,
document.querySelector('#root')
);
History.js
import { createBrowserHistory as history } from 'history';
export default history({});
Privateroutes.js
import React, { Fragment, useContext } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { AuthContext, checkAuth } from './checkAuth';
import App from './components/App';
import Home from './components/home';
import Admin from './components/admin';
import Login from './components/auth/Login';
import Unauthorized from './components/Unauthorized ';
import Notfound from './components/404';
const ProtectedRoute = ({ component: Component, roleType, ...rest })=> {
const authorization = useContext(AuthContext);
const [hasRequiredRole] = checkAuth({authorization, roleType});
return (
<Route
{...rest}
render={props => hasRequiredRole ?
<Component {...props} /> :
<Unauthorized {...props} /> }
/>)};
const Privateroutes = props => {
const { accessToken, authorization } = props.authData;
if (accessToken) {
return (
<Fragment>
<AuthContext.Provider value={authorization}>
<App>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" render={() => <Redirect to="/" />} />
<Route exact path="/home" component={Home} />
<ProtectedRoute
exact
path="/admin"
component={Admin}
roleType="admin"
/>
<Route path="/404" component={Notfound} />
<Route path="*" render={() => <Redirect to="/404" />} />
</Switch>
</App>
</AuthContext.Provider>
</Fragment>
);
} else {
return (
<Fragment>
<Route exact path="/login" component={Login} />
<Route exact path="*" render={() => <Redirect to="/login" />} />
</Fragment>
);
}
};
// my user reducer sample
// const accessToken = localStorage.getItem('token')
// ? JSON.parse(localStorage.getItem('token')).accessToken
// : false;
// const initialState = {
// accessToken: accessToken ? accessToken : null,
// authorization: accessToken
// ? jwtDecode(JSON.parse(localStorage.getItem('token')).accessToken)
// .authorization
// : null
// };
// export default function(state = initialState, action) {
// switch (action.type) {
// case actionTypes.FETCH_LOGIN_SUCCESS:
// let token = {
// accessToken: action.payload.token
// };
// localStorage.setItem('token', JSON.stringify(token))
// return {
// ...state,
// accessToken: action.payload.token,
// authorization: jwtDecode(action.payload.token).authorization
// };
// default:
// return state;
// }
// }
const mapStateToProps = state => {
const { authData } = state.user;
return {
authData: authData
};
};
export default connect(mapStateToProps)(Privateroutes);
checkAuth.js
import React from 'react';
export const AuthContext = React.createContext();
export const checkAuth = ({ authorization, roleType }) => {
let hasRequiredRole = false;
if (authorization.roles ) {
let roles = authorization.roles.map(item =>
item.toLowerCase()
);
hasRequiredRole = roles.includes(roleType);
}
return [hasRequiredRole];
};
DECODED JWT TOKEN SAMPLE
{
"authorization": {
"roles": [
"admin",
"operator"
]
},
"exp": 1591733170,
"user_id": 1,
"orig_iat": 1591646770,
"email": "hemanthvrm#stackoverflow",
"username": "hemanthvrm"
}
const Root = ({ session }) => {
const isLoggedIn = session && session.getCurrentUser
return (
<Router>
{!isLoggedIn ? (
<Switch>
<Route path="/signin" component={<Signin />} />
<Redirect to="/signin" />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/something-else" component={SomethingElse} />
<Redirect to="/" />
</Switch>
)}
</Router>
)
}
install react-router-dom
then create two components one for valid users and other for invalid users.
try this on app.js
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';
import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;
class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path="/" render={() =>(
loggedin ? ( <Route component={ValidUser} />)
: (<Route component={InValidUser} />)
)} />
</div>
</Router>
)
}
}
export default App;
Based on the answer of #Tyler McGinnis. I made a different approach using ES6 syntax and nested routes with wrapped components:
import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'
const PrivateRoute = ({ children, authed, ...rest }) =>
<Route
{...rest}
render={(props) => authed ?
<div>
{Children.map(children, child => cloneElement(child, { ...child.props }))}
</div>
:
<Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
export default PrivateRoute
And using it:
<BrowserRouter>
<div>
<PrivateRoute path='/home' authed={auth}>
<Navigation>
<Route component={Home} path="/home" />
</Navigation>
</PrivateRoute>
<Route exact path='/' component={PublicHomePage} />
</div>
</BrowserRouter>
Heres how I solved it with React and Typescript. Hope it helps !
import * as React from 'react';
import { FC } from 'react';
import { Route, RouteComponentProps, RouteProps, Redirect } from 'react-router';
const PrivateRoute: FC<RouteProps> = ({ component: Component, ...rest }) => {
if (!Component) {
return null;
}
const isLoggedIn = true; // Add your provider here
return (
<Route
{...rest}
render={(props: RouteComponentProps<{}>) => isLoggedIn ? (<Component {...props} />) : (<Redirect to={{ pathname: '/', state: { from: props.location } }} />)}
/>
);
};
export default PrivateRoute;
<PrivateRoute component={SignIn} path="/signin" />
I know it's been a while but I've been working on an npm package for private and public routes.
Here's how to make a private route:
<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>
And you can also make Public routes that only unauthed user can access
<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>
I hope it helps!
I implemented using-
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
authenticate props will be passed to components e.g. signup using which user state can be changed. Complete AppRoutes-
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';
import { config } from '../utils/Config';
export default class AppRoutes extends React.Component {
constructor(props) {
super(props);
// initially assuming that user is logged out
let user = {
isLoggedIn: false
}
// if user is logged in, his details can be found from local storage
try {
let userJsonString = localStorage.getItem(config.localStorageKey);
if (userJsonString) {
user = JSON.parse(userJsonString);
}
} catch (exception) {
}
// updating the state
this.state = {
user: user
};
this.authenticate = this.authenticate.bind(this);
}
// this function is called on login/logout
authenticate(user) {
this.setState({
user: user
});
// updating user's details
localStorage.setItem(config.localStorageKey, JSON.stringify(user));
}
render() {
return (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
<Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
</Switch>
);
}
}
Check the complete project here: https://github.com/varunon9/hello-react
The accepted answer is good, but it does NOT solve the problem when we need our component to reflect changes in URL.
Say, your component's code is something like:
export const Customer = (props) => {
const history = useHistory();
...
}
And you change URL:
const handleGoToPrev = () => {
history.push(`/app/customer/${prevId}`);
}
The component will not reload!
A better solution:
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import store from '../store/store';
export const PrivateRoute = ({ component: Component, ...rest }) => {
let isLoggedIn = !!store.getState().data.user;
return (
<Route {...rest} render={props => isLoggedIn
? (
<Component key={props.match.params.id || 'empty'} {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
} />
)
}
Usage:
<PrivateRoute exact path="/app/customer/:id" component={Customer} />
I love #fermmm answer but in his implementation the rendered component will not match with the url if the user is not logged in. Thus it might be confusing for a visitor.
So, instead of
return (
<Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
);
I would suggest using:
return (
<Route {...props}>
{userIsLogged ? (
props.children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location },
}}
/>
)}
</Route>
);
In this case you will still get the component rendered but "/login" in the URL instead of the previous route segment.
It seems your hesitation is in creating your own component and then dispatching in the render method? Well you can avoid both by just using the render method of the <Route> component. No need to create a <AuthenticatedRoute> component unless you really want to. It can be as simple as below. Note the {...routeProps} spread making sure you continue to send the properties of the <Route> component down to the child component (<MyComponent> in this case).
<Route path='/someprivatepath' render={routeProps => {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <MyComponent {...routeProps} anotherProp={somevalue} />
} />
See the React Router V4 render documentation
If you did want to create a dedicated component, then it looks like you are on the right track. Since React Router V4 is purely declarative routing (it says so right in the description) I do not think you will get away with putting your redirect code outside of the normal component lifecycle. Looking at the code for React Router itself, they perform the redirect in either componentWillMount or componentDidMount depending on whether or not it is server side rendering. Here is the code below, which is pretty simple and might help you feel more comfortable with where to put your redirect logic.
import React, { PropTypes } from 'react'
/**
* The public API for updating the location programatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
static defaultProps = {
push: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
}
isStatic() {
return this.context.router && this.context.router.staticContext
}
componentWillMount() {
if (this.isStatic())
this.perform()
}
componentDidMount() {
if (!this.isStatic())
this.perform()
}
perform() {
const { history } = this.context.router
const { push, to } = this.props
if (push) {
history.push(to)
} else {
history.replace(to)
}
}
render() {
return null
}
}
export default Redirect
My Previous answer is not scalable. Here is what I think is good approach-
Your Routes-
<Switch>
<Route
exact path="/"
component={matchStateToProps(InitialAppState, {
routeOpen: true // no auth is needed to access this route
})} />
<Route
exact path="/profile"
component={matchStateToProps(Profile, {
routeOpen: false // can set it false or just omit this key
})} />
<Route
exact path="/login"
component={matchStateToProps(Login, {
routeOpen: true
})} />
<Route
exact path="/forgot-password"
component={matchStateToProps(ForgotPassword, {
routeOpen: true
})} />
<Route
exact path="/dashboard"
component={matchStateToProps(DashBoard)} />
</Switch>
Idea is to use a wrapper in component props which would return original component if no auth is required or already authenticated otherwise would return default component e.g. Login.
const matchStateToProps = function(Component, defaultProps) {
return (props) => {
let authRequired = true;
if (defaultProps && defaultProps.routeOpen) {
authRequired = false;
}
if (authRequired) {
// check if loginState key exists in localStorage (Your auth logic goes here)
if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
return <Component { ...defaultProps } />; // authenticated, good to go
} else {
return <InitialAppState { ...defaultProps } />; // not authenticated
}
}
return <Component { ...defaultProps } />; // no auth is required
};
};
Here is the simple clean protected route
const ProtectedRoute
= ({ isAllowed, ...props }) =>
isAllowed
? <Route {...props}/>
: <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=>
<Switch>
<Route exact path="/authentificate" component={Login}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/secrets"
component={Secrets}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/polices"
component={Polices}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/grants" component={Grants}/>
<Redirect from="/" to={lastTab}/>
</Switch>
isTokenVerified is a method call to check the authorization token basically it returns boolean.
This is just a basic approach for beginners not for professional redux developers
import React, { useState, useEffect } from "react";
import {
Route,
BrowserRouter as Router,
Switch,
Redirect,
} from "react-router-dom";
import Home from "./components/Home";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
function App() {
const [isAuth, setAuth] = useState(false);
const checkAuth = () => {
// Your auth logic here
setAuth(true);
};
useEffect(() => {
checkAuth();
});
return (
<Router>
<Switch>
<Route
path="/user/dashboard"
render={(props) =>
isAuth ? <Dashboard {...props} /> : <Redirect to="/" />
}
/>
<Route path="/login" component={Login} />
<Route path="/" component={Home} />
</Switch>
</Router>
);
}
Here is my own approach
const RedirectionUnit = () => {
const [user] = useContext(AuthContext);
const pathname = useLocation().pathname;
let redirectTo;
if (user === null) redirectTo = "login";
else if (pathname === "/")
if (user.type === "supervisor" ) redirectTo = "all-parteners";
else if (user.type === "manager" ) redirectTo = "all-employees";
else if (user.type === "employee" ) redirectTo = "unfinished-tasks";
if (redirectTo && '/' + redirectTo !== pathname)
return <Redirect to={redirectTo} />;
return null;
};
const NavigationRoutes = () => {
return (
<>
<Route component={RedirectionUnit} />
{/* prettier-ignore */}
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
<Route exact path="/new-parteners" component={NewParteners} />
<Route exact path="/all-parteners" component={AllParteners} />
<Route exact path="/new-employees" component={NewEmployees} />
<Route exact path="/all-employees" component={AllEmployees} />
<Route exact path="/unfinished-tasks" component={UnfinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route exact path="/finished-tasks" component={FinishedTasks} />
<Route component={NotFound} />
</Switch>
</>
);
};
I was looking for a solution where my main router file had everything it needed to authenticate the routes. No nested component needed or complicated if else's. Below is my approach
import React from "react";
import { Routes, Route } from "react-router-dom";
import { Navigate } from "react-router-dom";
// Other imports
export default function AppRoutes() {
// This coming from react-redux
// After a user is logged in this will set in the global state
const { currentUser } = useCurrentUser();
const landing = <Landing />
const authenticate = (component) => {
return currentUser ? component : <Navigate to="/" />;
}
return (
<Routes>
<Route path="/" element={currentUser ? <Home /> : landing} />
<Route path="/blogs/:id" element={authenticate(<Blog />)} />
<Route path="/blogs/:id/edit" element={authenticate(<BlogEdit />)} />
<Route path="/profile" element={authenticate(<Profile />)} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
);
}
Based on the solution of #MaxThom for TypeScript, here is an option to be able to pass a component or a render function to PrivateRoute:
import React from "react";
import { Route, Redirect, RouteProps, RouteComponentProps } from "react-router-dom";
const PrivateRoute: React.FC<RouteProps> = ({component, render, ...rest}) => {
const userIsLogged = window.localStorage.getItem('currentUsecase');
if (userIsLogged === undefined) return (
<Route render={
(props: RouteComponentProps<{}>) => <Redirect
to={{ pathname: '/', state: { from: props.location } }}
/>
}/>
)
return (
<Route {...rest} render={render} component={component} />
)
};
export default PrivateRoute;
I hope this helps.
I was also looking for some answer. Here all answers are quite good, but none of them give answers how we can use it if user starts application after opening it back. (I meant to say using cookie together).
No need to create even different privateRoute Component. Below is my code
import React, { Component } from 'react';
import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './stores';
import requireAuth from './components/authentication/authComponent'
import SearchComponent from './components/search/searchComponent'
import LoginComponent from './components/login/loginComponent'
import ExampleContainer from './containers/ExampleContainer'
class App extends Component {
state = {
auth: true
}
componentDidMount() {
if ( ! Cookies.get('auth')) {
this.setState({auth:false });
}
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route exact path="/searchComponent" component={requireAuth(SearchComponent)} />
<Route exact path="/login" component={LoginComponent} />
<Route exact path="/" component={requireAuth(ExampleContainer)} />
{!this.state.auth && <Redirect push to="/login"/> }
</Switch>
</BrowserRouter>
</Provider>);
}
}
}
export default App;
And here is authComponent
import React from 'react';
import { withRouter } from 'react-router';
import * as Cookie from "js-cookie";
export default function requireAuth(Component) {
class AuthenticatedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: Cookie.get('auth')
}
}
componentDidMount() {
this.checkAuth();
}
checkAuth() {
const location = this.props.location;
const redirect = location.pathname + location.search;
if ( ! Cookie.get('auth')) {
this.props.history.push(`/login?redirect=${redirect}`);
}
}
render() {
return Cookie.get('auth')
? <Component { ...this.props } />
: null;
}
}
return withRouter(AuthenticatedComponent)
}
Below I have written blog, you can get more depth explanation there as well.
Create Protected routes in ReactJS

Categories