react - react router - privateRoute - infinite loop - javascript

I'm trying to make a simple authentication system in my Single Page Application.
I want to disable all routes execpt /login for guest.
The way to know if the user is authenticated or a guest it's to know if there are an access_token in the localStorage.
When I'm starting the app the Main component is started. The component define the routes and know if the user is authenticated by check the localStorage.
The default route (/) is for render the Home component but, like this example of react router the Home component is protected by a PrivateRoute object.
The PrivateRoute object check if the user is authenticated. If yes, Home component is rendered, else the user is redirected to the logincomponent at /login.
The Login component redirect the user to / in case of success and execute a callback to give the access_token.
The Main component define the callback, It's about to save the access_token in the localStorage and change the state for declare the user as authenticated. And now, the user can access the Home component.
My problem is, the PrivateRoute system always the check the user as a guest so it's always a redirection to /login. But when the are an access_token in localStorage the Login component redirect to the Home protected by the PrivateRoute and this is an infite loop despite the handleLogin callback.
Can you find a solution ?
Main.jsx
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Link, Redirect, Route} from "react-router-dom";
import {Login} from "./Login"
import {Home} from "./Home";
class Main extends Component {
constructor(props) {
super(props);
this.handleLogout = this.handleLogout.bind(this);
this.handleLogin = this.handleLogin.bind(this);
this.state = {authed: localStorage.getItem('access_token') !== null};
}
componentDidCatch(error, info) {
}
handleLogout(event) {
event.preventDefault();
localStorage.removeItem('access_token');
this.setState({authed: false});
}
handleLogin(token) {
localStorage.setItem('access_token', token);
this.setState({authed: token !== null});
}
render() {
const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={props =>
this.state.authed()
? (<Component {...props} />)
: (<Redirect to="/login"/>)
}
/>
);
const LoginLogout = () => {
return this.state.authed
? (<button onClick={this.handleLogout}>Logout</button>)
: (<Link to="/login">Login</Link>);
};
return (
<BrowserRouter>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<LoginLogout/>
</li>
</ul>
<Route path="/login" component={() => <Login handleLogin={this.handleLogin}/>}/>
<PrivateRoute exact path="/" component={Home}/>
</div>
</BrowserRouter>
);
}
}
if (document.getElementById('main')) {
ReactDOM.render(<Main/>, document.getElementById('main'));
}
Login.jsx
import React, {Component} from 'react';
import {Redirect} from "react-router-dom";
export class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
redirect: localStorage.getItem('access_token') !== null,
token: null,
loading: false,
error: null
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentWillUnmount() {
this.props.handleLogin(this.state.token);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
this.setState({
error: null,
loading: true
});
axios.post('/api/login', {
'client_id': '3',
'email': this.state.email,
'password': this.state.password,
'confirm_password': this.state.password
}).then((response) => {
let token = response.data.access_token;
this.setState({
redirect: true,
token: token,
loading: false
});
}, (error) => {
console.error('error', error.response.data);
this.setState({
error: error.response.data,
loading: false
});
});
}
render() {
if (this.state.redirect)
return (<Redirect to={"/"}/>);
return (
<form onSubmit={this.handleSubmit}>
<label htmlFor="email">Email :</label>
<input type="text" name="email" id="email" value={this.state.email} onChange={this.handleInputChange}
disabled={this.state.loading}/>
<label htmlFor="password">Password :</label>
<input type="password" name="password" id="password" value={this.state.password}
onChange={this.handleInputChange} disabled={this.state.loading}/>
<button type="submit"
disabled={this.state.loading}>{this.state.loading ? "..." : "Se connecter"}</button>
{this.state.error && (
<div>
<p>Erreur : {JSON.stringify(this.state.error)}</p>
</div>
)}
</form>
);
}
}

In your handleSubmit function you need to call the handleLogin from props so the state is correctly updated in your container component.
handleSubmit(event) {
...
.then((response) => {
let token = response.data.access_token;
this.setState({
redirect: true,
token: token,
loading: false
});
// here, call the handle from props
this.props.handleLogin(token);
}
...
That way, your this.state.authed will have the proper value

Related

How to save auth status after submitting post

i have this blog-like app and there is a problem that every time i sumbit a post my authorisation status is refreshing and im unlogged again.
Idk if i should save the status with localstorage and how to properly do that or there is better way, maybe cookies. I will be very thankful for any help
CreatePost.js
import { addDoc, collection } from 'firebase/firestore';
import { db } from '../firebaseconf';
class CreatePost extends Component {
constructor(props) {
super(props);
this.state = {
setTitle: "",
setPostText: "",
};
}
sTitle = (event) => {
this.setState({ setTitle: (event.target.value) });
}
sPostText = (event) => {
this.setState({ setPostText: (event.target.value) });
}
collectionRef = collection(db, "posts");
createPost = async () => {
await addDoc(this.collectionRef, { title: this.state.setTitle || null, postText: this.state.setPostText || null });
window.location.pathname = "/";
}
render() {
return (
<div className="cpPage">
<div className="cpContainer">
<h1>Create a Post</h1>
<div className="inputGp">
<label>Title:</label>
<input
placeholder="Title..."
onChange={this.sTitle}
/>
</div>
<div className="inputGp">
<label>Post:</label>
<textarea
placeholder="Write your post..."
onChange={this.sPostText}
/>
</div>
<button onClick={this.createPost}>Add your post</button>
</div>
</div>
);
}
}
export default CreatePost;
App.js
import React, { Component } from 'react';
import './css/main.css';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import CreatePost from './pages/CreatePost';
import Login from './pages/Login';
import { signOut } from "firebase/auth";
import { auth } from "./firebaseconf";
class App extends Component {
constructor(props) {
super(props);
this.state = {
isAuth: false,
setIsAuth: false
};
}
signIn = () => {
this.setState({ setIsAuth: true });
}
signUserOut = () => {
signOut(auth).then(() => {
localStorage.clear();
this.setState({ setIsAuth: false });
window.location.pathname = "/login";
});
}
render() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
{!this.state.setIsAuth ? (
<Link to="/login"> Login </Link>
) : (
<>
<Link to="/createpost"> Create Post </Link>
<button onClick={this.signUserOut}> Log Out</button>
</>)}
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login setIsAuth={this.signIn} />} />
<Route path="/createpost" element={<CreatePost setIsAuth={this.signIn} />} />
</Routes>
</Router>
);
}
}
export default App;
Firstly keeping track of if there is a authenticated user as you do is unnecessary - instead check if Firebases built in auth().currentUser is/isn’t null. Firebase for react automatically handles all auth cookies for you automatically.
Secondly use router to redirect/change pages - changing the window location can/will reload the app and the current user will briefly be null as Firebase will be briefly asynchronous be checking the cookies/re-authorizing the user.
Thirdly Firebase for react has a built in auth().onAuthStateChanged((user) => {}) stream you can listen to to make any updates when the auth state changes.

Why won't my endpoint's data display on the browser on successful auth?

I just don't understand why the user's name that's coming from my endpoint won't show up on the browser on successful auth (when user logs in successfully using their credentials). When the user logs in, I want them to be greeted with a simple Hello, *name* but the *name* won't show up. I'm successfully grabbing the name via console.log(response.data[0].name); so I know the name's definitely coming through based on what I see in the console.
I've tried too many ways to list in order to solve this issue but ran out of options. I know it's something minuscule I'm overlooking but just can't spot what, I just need another pair of eyes to see what's wrong in my code.
How can I solve this?
Here's login component:
import React, { Component } from 'react';
import { Link, Redirect } from "react-router-dom";
import axios from "axios";
import Cookies from 'js-cookie';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
remember_me: true,
name: '',
logged_in_flag: false,
access_token: null,
auth: false
}
this.emailHandler = this.emailHandler.bind(this);
this.passwordHandler = this.passwordHandler.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
emailHandler(e) {
this.setState({
email: e.target.value,
});
}
passwordHandler(e) {
this.setState({
password: e.target.value,
});
}
handleSubmit(e) {
e.preventDefault();
const user = {
email: this.state.email,
password: this.state.password,
remember_me: this.state.remember_me
}
JSON.stringify(user);
const headers = {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
const { history } = this.props;
axios.post('http://127.0.0.1:8000/api/auth/login', user, {headers})
.then(response => {
console.log(response.data[0].name);
console.log(response.data.access_token);
let set_access_token = Cookies.set("access_token", response.data.access_token);
this.setState({
logged_in_flag: true,
name: response.data[0].name,
access_token: set_access_token,
auth: true
});
Cookies.set("access_token", this.state.access_token);
history.push('wall-of-fame');
}).catch(error => {
console.log("AXIOS ERROR!! " + error);
});
}
render() {
const {name, auth} = this.state;
console.log(this.state.name);
console.log(this.state.auth);
if(auth) {
console.log("ARE WE HERE");
return(
<Redirect to={{
pathname: "/wall-of-fame",
state: {name: name}
}} />
);
}
return(
<div id="login">
<div className="container">
<div id="login-row" className="row justify-content-center align-items-center">
<div id="login-column" className="col-md-6">
<div id="login-box" className="col-md-12">
<form id="login-form" className="form" method="post" onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="email" className="text-info">Email:</label><br/>
<input type="text" name="email" id="email" className="form-control" onChange={this.emailHandler}/>
</div>
<div className="form-group">
<label htmlFor="password" className="text-info">Password:</label><br/>
<input type="password" name="password" id="password" className="form-control" onChange={this.passwordHandler}/>
</div>
<Link to='/wall-of-fame'>
<div className="form-group">
<input type="submit" name="submit" className="btn btn-info btn-md" value="Submit"/>
</div>
</Link>
<Link to="/register">
<div id="register-link" className="text-right">
Register here
</div>
</Link>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Login;
Here's Wall component:
import React, { Component } from 'react'
import { withRouter } from "react-router-dom";
class Wall extends Component {
constructor(props) {
super(props);
}
render() {
return(
<h1>Hello, {this.props.name}!</h1>
);
}
}
export default withRouter(Wall);
Here's App component:
import React, {Component} from 'react';
import {Switch, Route, Redirect} from 'react-router-dom';
import Login from './components/Login/Login';
import Wall from './components/Wall/Wall';
import Cookies from 'js-cookie';
class App extends Component {
constructor(props) {
super(props);
this.state = {
auth: false
};
}
componentDidMount() {
// I'm not Unsure if I need to do anything here
if(Cookies.get("access_token") !== null) {
console.log("it's true!");
this.setState({auth: true});
} else {
console.log("it's false");
}
}
render() {
return (
<React.Fragment>
<Switch>
<Route path="/login" component={Login}/>
<Route path="/wall-of-fame" component={Wall}/>
</Switch>
</React.Fragment>
);
}
}
export default App;
You're using Routing, so you need to use routes and links.
This:
authSuccess(authenticated) {
const {name} = this.state;
if(authenticated) {
console.log("ARE WE HERE");
return(
<Wall name={name}/>
);
}
return <Login/>;
}
Should navigate to /wall-of-fame instead of returning a component. Your render function isn't rendering the output of authSuccess, so it won't care what's being returned. You need to use react-router's functions to navigate to the correct link.
Have a search for "React router, navigate after login" or something. You'll find good answers such as this question: Navigate to page after authentication using React Router v4

call mapDispatchToProps inside axios success not working

I am trying to navigate to another page after a successful axios call which dispatch the action to change the state. But, I am sure that i wrongly call dispatch inside the axios call which result below error. I have tried many other ways like using ES6 arrow function to call the dispatch method but never works.
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
App.js
import React, { Component } from 'react';
import axios from 'axios';
import history from './History';
import { Redirect } from "react-router-dom";
import './App.css';
import { connect } from 'react-redux';
import * as actionType from './reducer/action';
class App extends Component {
constructor(){
super();
this.state = {
username: '',
password: '',
loginData: []
};
this.handleUserChange = this.handleUserChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event){
event.preventDefault();
axios.post('https://api.github.com/user',{}, {
auth: {
username: this.state.username,
password: this.state.password
}
}).then((response) => {
console.log(response.data);
this.props.onLoginAuth(true);// This is where i am getting ERROR
//history.push({pathname: '/home', state: { detail: response.data }});
//history.go('/home');
this.setState({
loginData : response.data,
});
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
handleUserChange(event){
this.setState({
username : event.target.value,
});
}
handlePasswordChange = event => {
this.setState({
password: event.target.value
});
}
render() {
if(this.props.authenticated){
console.log("Redirecting to Home page " + this.props.authenticated);
return <Redirect to={{ pathname: '/home', state: { detail: this.state.loginData } }}/>
}
return (
<div className='loginForm'>
<form onSubmit={this.handleSubmit}>
<label>
username :
<input type="text" value={this.state.username} onChange={this.handleUserChange} required/>
</label>
<label>
password :
<input type="password" value={this.state.password} onChange={this.handlePasswordChange} required/>
</label>
<input type="submit" value="LogIn" />
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
authenticated: state.authenticated
};
}
const mapDispatchToProps = dispatch => {
return {
onLoginAuth: (authenticated) => dispatch({type: actionType.AUTH_LOGIN, authenticated:authenticated})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import PrivateRoute from './PrivateRoute';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import {
Router,
Redirect,
Route,
Switch
} from "react-router-dom";
import Home from './Home';
import User from './User';
import history from './History';
import reducer from './reducer/Login';
const store = createStore(reducer);
ReactDOM.render(
<Provider store= {store} >
<Router history={history}>
<Switch>
<Route path="/" exact component={App} />
<PrivateRoute path="/home" component={Home} />
<PrivateRoute path="/user" component={User} />
</Switch>
</Router>
</Provider>,
document.getElementById('root'));
registerServiceWorker();
Home.js
import React, { Component } from 'react';
import axios from 'axios';
import Autosuggest from 'react-autosuggest';
import './Home.css';
import history from './History';
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
const getSuggestionValue = suggestion => suggestion;
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion}
</div>
);
class Home extends Component {
constructor(props) {
super(props);
// Autosuggest is a controlled component.
// This means that you need to provide an input value
// and an onChange handler that updates this value (see below).
// Suggestions also need to be provided to the Autosuggest,
// and they are initially empty because the Autosuggest is closed.
this.state = {
value: '',
suggestions: [],
timeout: 0
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
console.log('=====++++ ' + newValue);
};
onSuggestionSelected = (event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) => {
console.log("Get the user +++++++++ " + suggestionValue);
if(suggestionValue && suggestionValue.length >= 1){
axios.get('https://api.github.com/users/'+ suggestionValue)
.then((response) => {
console.log("user selected : "+ response.data.avatar_url);
history.push({pathname: '/user', state: { detail: response.data }});
history.go('/user');
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.getSuggestions();
}, 500);
};
getSuggestions = () =>{
if(this.state.value && this.state.value.length >= 1){
axios.get('https://api.github.com/search/users',{
params: {
q: this.state.value,
in:'login',
type:'Users'
}
}).then((response) => {
console.log("users login : "+ response.data.items);
const userNames = response.data.items.map(item => item.login);
console.log("===== " + userNames);
this.setState({
suggestions: userNames
})
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Type a userName',
value,
onChange: this.onChange
};
return (
<div>
<div>
Home page {this.props.location.state.detail.login}
</div>
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={this.onSuggestionSelected}
/>
</div>
</div>
);
}
}
export default Home;
PrivateRouter.js
import React from 'react';
import {
Redirect,
Route,
} from "react-router-dom";
const PrivateRoute = ({ component: Component, ...rest}) => (
<Route
{...rest}
render={props =>
props.authenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute;
Can you help me how i can call onLoginAuth from axios?
I don't think that the problem comes from the axios call. Your axios call the dispatch which update the state authenticated. So everytime the state authenticated is updated, the render method of App component is called.
The condition if(this.props.authenticated) is verified, and Redirect to /home is called. But somehow, your router routes again to /. The App component is rerender. The condition if(this.props.authenticated) is true again, and the routes routes again to App. It creates an infinite loop, that's why you see the message Maximum update depth exceeded.
In the index.js, for testing purpose, replace all PrivateRoute by Route to see if the route works correctly. If it works, the problem may come from your PrivateRoute component.

React login authentication in child components

I have a App component where user login into the application and the remaining components must verify auth guard before rendering, otherwise redirect to login page i.e. App component.
I am trying to pass the state variable from App component to the child components via a PrivateRouter as my auth guard. But its not working. Before this i have tried also using react-router v4 to use render inside the route.
App.js
import React, { Component } from 'react';
import axios from 'axios';
import history from './History';
import './App.css';
class App extends Component {
constructor(){
super();
this.state = {
username: '',
password: '',
authenticated: false
};
this.handleUserChange = this.handleUserChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event){
event.preventDefault();
axios.post('https://api.github.com/user',{}, {
auth: {
username: this.state.username,
password: this.state.password
}
}).then((response) => {
console.log(response.data);
this.setState({
authenticated : true,
});
history.push({pathname: '/home', state: { detail: response.data }});
history.go('/home');
}).catch(function(error) {
this.setState({
authenticated : false,
});
console.log('Error on Authentication' + error);
});
}
handleUserChange(event){
this.setState({
username : event.target.value,
});
}
handlePasswordChange = event => {
this.setState({
password: event.target.value
});
}
render() {
return (
<div className='loginForm'>
<form onSubmit={this.handleSubmit}>
<label>
username :
<input type="text" value={this.state.username} onChange={this.handleUserChange} required/>
</label>
<label>
password :
<input type="password" value={this.state.password} onChange={this.handlePasswordChange} required/>
</label>
<input type="submit" value="LogIn" />
</form>
</div>
);
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import PrivateRoute from './PrivateRoute';
import {
Router,
Redirect,
Route,
Switch
} from "react-router-dom";
import Home from './Home';
import User from './User';
import history from './History';
ReactDOM.render(
<Router history={history}>
<Switch>
<Route path="/" exact component={App} />
<PrivateRoute path="/home" component={Home} />
<PrivateRoute path="/user" component={User} />
</Switch>
</Router>,
document.getElementById('root'));
registerServiceWorker();
Home.js
import React, { Component } from 'react';
import axios from 'axios';
import Autosuggest from 'react-autosuggest';
import './Home.css';
import history from './History';
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
const getSuggestionValue = suggestion => suggestion;
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion}
</div>
);
class Home extends Component {
constructor(props) {
super(props);
// Autosuggest is a controlled component.
// This means that you need to provide an input value
// and an onChange handler that updates this value (see below).
// Suggestions also need to be provided to the Autosuggest,
// and they are initially empty because the Autosuggest is closed.
this.state = {
value: '',
suggestions: [],
timeout: 0
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
console.log('=====++++ ' + newValue);
};
onSuggestionSelected = (event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) => {
console.log("Get the user +++++++++ " + suggestionValue);
if(suggestionValue && suggestionValue.length >= 1){
axios.get('https://api.github.com/users/'+ suggestionValue)
.then((response) => {
console.log("user selected : "+ response.data.avatar_url);
history.push({pathname: '/user', state: { detail: response.data }});
history.go('/user');
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
if(this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.getSuggestions();
}, 500);
};
getSuggestions = () =>{
if(this.state.value && this.state.value.length >= 1){
axios.get('https://api.github.com/search/users',{
params: {
q: this.state.value,
in:'login',
type:'Users'
}
}).then((response) => {
console.log("users login : "+ response.data.items);
const userNames = response.data.items.map(item => item.login);
console.log("===== " + userNames);
this.setState({
suggestions: userNames
})
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Type a userName',
value,
onChange: this.onChange
};
return (
<div>
<div>
Home page {this.props.location.state.detail.login}
</div>
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={this.onSuggestionSelected}
/>
</div>
</div>
);
}
}
export default Home;
PrivateRouter.js
import React from 'react';
import {
Redirect,
Route,
} from "react-router-dom";
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
props.authenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute;
How can pass state variable authenticated to PrivateRouter or is there any better way of doing this?

React Redirect should not use outside the router

I am fairly new to react and I am learning myself by creating this sample app where the user tries to login with credentials, On successful, the user should redirect to the home page where I may display some data. So far I am receiving below error
Uncaught Error: You should not use <Redirect> outside a <Router>
My two components look like below
App.js
import React, { Component } from 'react';
import axios from 'axios';
import { Route, Redirect } from 'react-router';
import Home from './Home';
import AuthLogin from './AuthLogin';
class App extends Component {
constructor(){
super();
this.state = {
username: '',
password: '',
userdata: [],
isLogin: false
};
this.handleUserChange = this.handleUserChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event){
event.preventDefault();
axios.post('https://api.github.com/user',{}, {
auth: {
username: this.state.username,
password: this.state.password
}
}).then((response) => {
console.log(response.data);
this.setState({
userdata: response.data,
isLogin:true
});
}).catch(function(error) {
console.log('Error on Authentication' + error);
});
}
handleUserChange(event){
this.setState({
username : event.target.value,
});
}
handlePasswordChange = event => {
this.setState({
password: event.target.value
});
}
render() {
if(this.state.isLogin){
return <Redirect to={{
pathname: 'home',
state: this.state.data
}} />
}
return (
<form onSubmit={this.handleSubmit}>
<label>
username :
<input type="text" value={this.state.username} onChange={this.handleUserChange} required/>
</label>
<label>
password :
<input type="password" value={this.state.password} onChange={this.handlePasswordChange} required/>
</label>
<input type="submit" value="LogIn" />
</form>
);
}
}
export default App;
Home.js
import React, { Component } from 'react';
import axios from 'axios';
class Home extends Component {
state = {
Search:'',
results:[]
}
getInfo = () => {
axios.get('https://api.github.com/search/users',{
params: {
q: this.state.Search,
in:'login',
type:'Users'
}
}).then(({ response }) => {
this.setState({
results: response.data.items.login
})
})
}
handleSearchChange(event){
this.setState({
Search: this.state.Search
}, setInterval(() => {
if (this.state.Search && this.state.Search.length > 1) {
if (this.state.Search.length % 2 === 0) {
this.getInfo();
}
}
},500));
}
render(){
return (
<div>
Home page {this.props.data}
<form>
<label>
Search :
<input type="text" placeholder="Search for..." value={this.state.Search} onChange={this.handleSearchChange} />
</label>
</form>
</div>
);
}
}
export default Home;
My aim is to get a page after a successful login.
Can I get help? Much appreciated.
Thanks
Make sure you enclose your <App/> with <BrowserRouter> in your index.js.
ReactDom.render(<BrowserRouter><App/></BrowserRouter>,document.getElementById('root'));
(Use: import {BrowserRouter} from 'react-router-dom')
Instead of using Redirect component you can use,
this.props.history.push(<path>)
If you need any help about this, you can check this doc -> https://tylermcginnis.com/react-router-programmatically-navigate/
As your component is outside the scope of router, it's history parameter will be null. So to overcome that issue, you need to pass a history in you App tag, something like this
import history from './history';
<App history={history}>
In that way you will be able to access the history inside you App class. You will have to create a file:
history.js
import createHashHistory from 'history/createBrowserHistory';
export default createHashHistory();
I faced the same issue quite recently.
Actually issue is due to multiple version of react-router-dom, so because in that way you will have multiple instances of react-router and therfore it will seem that Redirect is outside Router.
One of my dependency was using older version of react-router-dom, by upgrading that version, i was able to solve the issue

Categories