React Router v4: custom PrivateRoute component keeps reloading - javascript

this is my first ever question so please spare my life.
I tried doing all the research I could and I've given up.
so basically I'm dealing with protected routes with react-router v4. After looking at Tyler Mcginnis' PrivateRoute, I thought I was on the right track but nope.
I can correctly set isAuthenticated: true with the requireUser function, but the PrivateRoute route component never renders. It always redirects to /login. After further investigation, I realized that somehow the page refreshes and therefore sets isAuthenticated back to false
also, I'm not using Redux.
OH, and any critics to my code is welcomed.
pls help
/App.js
class App extends Component {
constructor() {
super()
this.state = {
isAuthenticated: false,
user: {
username: ''
}
}
}
requireUser = (userData) => {
console.log('userData', userData);
if(userData) {
this.setState({
isAuthenticated: true,
user: {
username: userData.username
}
})
}
}
render() {
return (
<Router>
<div>
<Navbar />
<Route exact path='/' component={Landing} />
<Route path='/register' component={Register} />
<Route path='/login' render={(props) => {
return <Login {...props} requireUser={this.requireUser} />
}} />
<PrivateRoute path='/search' component={SearchBar} auth={this.state.isAuthenticated} />
</div>
</Router>
);
}
}
export default App;
/PrivateRoute.js
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
const PrivateRoute = ({ component: Component, auth, path, ...rest}) => {
return <Route {...rest} path='/search' render={(props) => {
console.log(auth);
if(auth) {
return (
<Component {...props} />
)
} else {
return (
<Redirect to='/login' />
)
}
}} />
}
export default PrivateRoute
EDIT: added in my Login component
UPDATE: I ended up adding this.props.history.push('/search) to my handleOnSubmit function
/Login.js
import React, { Component } from 'react'
import axios from 'axios'
import jwt_decode from 'jwt-decode'
class Login extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: '',
}
}
handleOnChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
handleOnSubmit = (e) => {
// prevent page refresh
e.preventDefault()
// destructure state
const { email, password } = this.state
// assign to userData
const userData = { email, password }
// axios post /api/users/login
axios
.post('/api/users/login', userData)
.then((response) => {
// destructure
const { token } = response.data
const decoded = jwt_decode(token)
this.props.requireUser(decoded)
})
.catch((error) => {
console.log(error);
})
}
render() {
return (
<section className="section">
<div className="container">
<form>
<div className="field">
<label className="label">Email</label>
<div className="control">
<input
type="email"
placeholder="email"
className="input"
name='email'
onChange={this.handleOnChange}
/>
</div>
</div>
<div className="field">
<label className="label">Password</label>
<input
type="password"
placeholder="password"
className="input"
name='password'
onChange={this.handleOnChange}
/>
</div>
<div className="field">
<div className="control">
<button
onClick={this.handleOnSubmit}
type="submit"
className="button is-primary"
>Log In</button>
</div>
</div>
</form>
</div>
</section>
)
}
}
export default Login

You code looks sound. The issue is that when you manually change the address bar to /search, the browser will be refreshed. There is no way around that.
You could alternatively use a Link component with a to prop value of '/search' that your user can use to navigate to the search, or you could programmatically change the path with the history object.
this.props.history.push('/search');

Related

React: TypeError cannot read properties of undefined (reading 'push')

I am trying to create an app with a login functionality. The way I want it to work is that once the login is verified, the program would automatically redirect the user towards the homepage. Right now I have this code:
App.js:
function App() {
return (
<React.Fragment>
<div className="content">
<BrowserRouter>
<Routes>
<Route element={<LoginForm />} path="/login" />
<Route path="/test" element={<BasicExample />} />
</Routes>
</BrowserRouter>
</div>
</React.Fragment>
);
}
export default App;
Login
class LoginForm extends FForm {
// create a ref object
state = {
data: { username: "", password: "" },
errors: {},
};
handleSubmit = event => {
event.preventDefault();
const { email, password } = this.state;
// console.log(this.state);
console.log('logging')
axios
.post(
"http://127.0.0.1:8000/auth/",
{
username: email,
password: password
}
// { withCredentials: true }
)
.then(res => {
console.log('got it')
console.log(res);
window.localStorage.setItem("token", res.data.token);
console.log('pushing')
this.props.history.push('/test', { state: "sample data" })
// return <Redirect to="/home" />;
})
.catch(err => {
console.log('NOOOOO eror')
console.log(err);
});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<h1>welcome to log in!</h1>
<br />
<input
type="text"
name="email"
placeholder="Username"
defaultValue={this.state.email}
onChange={e => {
this.setState({ email: e.target.value });
}}
/>
<br />
<br />
<input
type="password"
name="password"
placeholder="Password"
defaultValue={this.state.password}
onChange={e => {
this.setState({ password: e.target.value });
}}
/>
<br />
<br />
<button type="submit"> Login </button>
</form>
</div>
);
}
}
export default LoginForm;
The code outputs TypeError: cannot read properties of undefined (reading 'push'). I assume that the history was not initialized. Where/how should I initialize the history?
For react-router 6 continue to use element, but use a HOC
Ok, react-router 6 changed the way they do things to only use element, but you have to pass props yourself, and if you're using a class based component then you need to use a HOC)
const LoginComponentWithHistory = (props) => <LoginForm {...props} history={useHistory()}/>
// route
<Route element={<LoginFormWithHistory/>} path="/login" />
See related interesting github convo: https://github.com/remix-run/react-router/issues/8146
For react-router 5
This needs to be passed as component
<Route component={LoginForm} path="/login" />
When you pass it as component, react-router passes in all the props for you (including history)
Try exporting LoginForm with HOC from React Router
export default withRouter(LoginForm);

instance.render is not a function. (functional component)

This is my first post.
I've been reading a lot about functional components and trying everything that I could, but nothing seems to work in my case. I am getting instance.render is not a function.
Hopefully some of you can see where my error is, as I am quite new to programming. This is my code:
App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import Login from "./components/Login"
import Feed from "./components/Feed"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom"
const App = () => {
const [isLoggedIn, setLoggedIn] = useState(false)
const handleLogin = token => {
if (!token) return
localStorage.setItem('token', token)
setLoggedIn(true)
}
const handleLogout = () => () => {
setLoggedIn(false)
localStorage.clear()
}
return (
<div className="App">
<Router>
<Feed isLoggedIn={isLoggedIn} logout={handleLogout} />
<Switch>
<Route
exact
path='/login'
component={(props) => (
<Login {...props} onLogin={handleLogin} />
)}>
</Route>
</Switch>
</Router>
</div>
)
}
export default App;
Login.js
import React from "react"
import axios from "axios"
import { Button, Form, FormGroup, Input } from 'reactstrap'
import "../css/Login.css"
import { Link, BrowserRouter as Router, Switch, Route } from "react-router-dom"
import Signin from "./Signin"
class Login extends React.Component {
constructor (props) {
super(props)
this.state = {
user_name: "",
password: "",
error: false,
loggedIn: false,
}
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
})
}
login = (event) => {
event.preventDefault()
const { user_name, password } = this.state
axios("http://localhost:7001/api/login", {
method: "POST",
data: {
user_name,
password
}
})
.then((response) => {
this.props.onLogin(response.data.token)
this.setState({ loggedIn: true })
this.feedRedirect()
console.log(response.data)
})
.catch((error) => {
console.log(error)
})
this.setState({
user_name: "",
password: "",
error: false,
})
}
feedRedirect = () => {
this.props.history.push('/feed')
}
render() {
const { user_name, password, error, loggedIn } = this.state
return (
<div className="login">
<Form className="login-container" onSubmit={this.login}>
<FormGroup>
<Input
value={this.state.user_name}
onChange={this.handleChange}
name="user_name"
type="text"
className="form-control mb-2"
placeholder="Username"
/>
</FormGroup>
<FormGroup>
<Input
value={this.state.password}
onChange={this.handleChange}
name="password"
type="password"
className="form-control mb-2"
placeholder="Password"
/>
</FormGroup>
<Button className="button-login" disabled={!user_name || !password}>
Log in
</Button>
<hr />
<Router>
<Link to="/signin"> Don't have an account? Sign up here</Link>
<Switch>
<Route path="/signin" component={Signin}>
</Route>
</Switch>
</Router>
</Form>
</div>
)
}
}
export default Login;
Any help will be appreciated. Thank you in advance.

Set value of prop in child component and pass value back to state of app.js, react

I feel like this is pretty simple and it's just my lack of experience with react.js holding me up, but I have my app.js file which has a state value isUserLoggedIn and I'm passing this into my Login.js file as a prop.
The values is getting to the login page because I can use a conditional there to render or console.log accordingly.
However, I can't seem to figure out how I can (using my handleSubmit function in Login.js) set the value of loggedIn in props to true and pass back to the app.js file.
I'm using this to render conditionally in app.js so all I need to do is be able to change the value of loggedIn in login.js and pass that back to app.js.
How can I do that here?
App.js
class App extends React.Component {
state = {
isUserLoggedIn: false
};
render() {
return (
<Router>
<Row className={css(styles.container)}>
<Column flexGrow={1} className={css(styles.mainBlock)}>
<div className={css(styles.content)}>
<Switch>
<Route path="/" exact component={(props) => <Login {...props} loggedIn = {this.state.isUserLoggedIn}/>} />
</Switch>
</div>
</Column>
</Row>
</Router>
);
}
}
}
export default App;
Login.js
import React, { useState } from "react";
import { Button, FormGroup, FormControl, FormLabel } from "react-bootstrap";
import "./Login.css";
export default function Login(props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
function validateForm() {
return email.length > 0 && password.length > 0;
}
async function handleSubmit(event) {
event.preventDefault();
try{
props.history.push("/");
}catch (e) {
alert(e.message);
}
}
return(
<div className="Login">
<form onSubmit={handleSubmit}>
<FormGroup controlId="email" bsSize="large">
<FormLabel>Email</FormLabel>
<FormControl
autoFocus
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
</FormGroup>
<FormGroup controlId="password" bsSize="large">
<FormLabel>Password</FormLabel>
<FormControl
value={password}
onChange={e => setPassword(e.target.value)}
type="password"
/>
</FormGroup>
<Button bsSize="large" disabled={!validateForm()} type="submit">
Login
</Button>
</form>
</div>
);
}
You need to pass a function from app.js to Login.js. Also in app.js, the state should be handled by the component which means you can put it in the constructor.
App.js
class App extends React.component{
constructor(props){
super(props);
this.state = {
isLoggedIn: false
}
}
handleLogin = () => {
//add your logic here
this.setState({
isLoggedIn: true
})
}
render(){
<div>
<Route path="/" exact component={(props) => <Login {...props} handleLoggedIn = {this.handleLogin}/>} />
</div>
}
}
Login.js
export default function Login(props) {
async function handleSubmit(event) {
event.preventDefault();
this.props.handleLoggedIn()
try{
props.history.push("/");
}catch (e) {
alert(e.message);
}
}
// other parts of your code
}
Between these two components, rather you are passing the state to login.js, you are passing a function downward. After the button has been clicked, the function in app.js will be triggered. Because the function set a new state, the component will re-render and hence update the component. Therefore, you are able to get the latest state.

this.props.history don't redirect

I don't know where I'm going wrong
by clicking the enter button I validate my user and use this.props.history.push ("/ home");
and it doesn't work
my login . js
class LoginForm extends Component {
constructor(props){
super(props);
this.state = {
login:'',
password:'',
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
async onSubmit(e){
e.preventDefault();
const {login, password } = this.state;
const response = await api.post('/login', { login,password });
const user = response.data.user.login;
const {jwt} = response.data;
localStorage.setItem('token', jwt);
localStorage.setItem('user', user);
this.props.history.push("/home");
}
onChange(e){
this.setState({[e.target.name]: e.target.value});
}
render() {
const { errors, login, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<label htmlFor="login">Login</label>
<input type="text" name="login" id="login" value={login} onChange={(e) => this.onChange(e)} placeholder="Informe seu login" />
<label htmlFor="password">Senha</label>
<input type="password" name="password" id="password" value={password} onChange={(e) => this.onChange(e)} placeholder="Informe sua senha"/>
<button className="btnEnt" type="submit">Entrar</button>
</form>
)
}
}
export default LoginForm;
my router . js:
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import Login from './pages/login/index';
import DashBoard from './pages/dashboard/index';
import PrivateRoute from './auth';
export default function Routes(){
console.log('a')
return(
<BrowserRouter>
<Switch>
<PrivateRoute path="/home" component = {DashBoard}/>
</Switch>
</BrowserRouter>
);
} <PrivateRoute path="/home" component = {DashBoard}/>
</Switch>
</BrowserRouter>
);
}
my auth . js :
import { Route, Redirect} from 'react-router-dom';
const isAuth = () => {
console.log('a');
if(localStorage.getItem('token') !== null) {
return true;
}
return false;
};
const PrivateRoute = ({component: Component, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isAuth() ? (
<Component {...props} />
): (
<Redirect
to={{
pathname: '/',
state: {message: 'Usuário não autorizado'}
}}
/>
)}
/>
);
}
export default PrivateRoute;
my dashboard / index
import React, { Component } from 'react';
import Home from '../../components/Home';
class DashBoard extends Component {
render() {
return (
<Home />
)
}
}
export default DashBoard;
error:
Unhandled Rejection (TypeError): Cannot read property 'push' of
undefined
Uncaught (in promise) TypeError: Cannot read property 'push' of
undefined I really do not know what I can is wrong is not redirecting,
I have tried all possible alternatives.
Before you can use history in your React component, you need to wrap it in withRouter.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
import history from 'add your path'
export default function Routes() {
console.log('a')
return (
<BrowserRouter history={history}>
<Switch>
<PrivateRoute exact path="/home" component={DashBoard} />
</Switch>
</BrowserRouter>
)
}
do changes in router file
import { withRouter } from 'react-router-dom';
export default withRouter(LoginForm);
add withRouter in this component

Navigating using react router

I want to redirect to my home route upon submitting my axios request to the api and gathering a valid response. As can be seen, Im trying to use context but in this case i get an error "context is undefined". How can i navigate to my home route in this case? I tried using history.push but that does not seem to work either. Any ideas would be much appreciated?
import React, {Component} from 'react'
import axios from 'axios'
import Home from './Home'
import {
BrowserRouter,
Link,
Route
} from 'react-router-dom'
class SignUp extends Component {
constructor(props){
super(props);
this.state = {
email: '',
password: ''
};
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
handleClick = () => {
axios
.post('http://localhost:9000/signup',{
email: this.state.email,
password: this.state.password
}).then(function(response){
console.log(response.data.success)
this.context.router.push('/home');
})
}
render(){
return(
<BrowserRouter>
<Route path="/" render={() => (
<div>
<h1> Sign Up</h1>
<input name="email" placeholder="enter your email" onChange={e => this.handleChange(e)}/>
<br/>
<input name="password" placeholder="enter your password" onChange={e => this.handleChange(e)}/>
<br/>
<button onClick={() => this.handleClick()}>submit</button>
<Route exact path="/home" component={Home}/>
</div>
)}/>
</BrowserRouter>
)
}
}
SignUp.contextTypes = {
router: React.PropTypes.func.isRequired
};
export default SignUp
Routes should be always level above of all your components (or containers). Then, when a component is "inside" router (in your case BrowserRouter) it will gain access to its context.
Also you have inside render function of another wich does not make sense at all.
So something like this should work:
import React, {Component} from 'react'
import axios from 'axios'
import Home from './Home'
import {
BrowserRouter,
Link,
Route
} from 'react-router-dom'
class SignUp extends Component {
constructor(props){
super(props);
this.state = {
email: '',
password: ''
};
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
handleClick = () => {
axios
.post('http://localhost:9000/signup',{
email: this.state.email,
password: this.state.password
}).then(function(response){
console.log(response.data.success)
this.context.router.push('/home');
})
}
render(){
return(
<div>
<h1> Sign Up</h1>
<input name="email" placeholder="enter your email" onChange={e => this.handleChange(e)}/>
<br/>
<input name="password" placeholder="enter your password" onChange={e => this.handleChange(e)}/>
<br/>
<button onClick={() => this.handleClick()}>submit</button>
</div>
)
}
}
SignUp.contextTypes = {
router: React.PropTypes.func.isRequired
};
class App extends Component {
render() {
return(
<BrowserRouter>
<Route path="/" component={SignUp} />
<Route exact path="/home" component={Home}/>
</BrowserRouter>)
}
}
export default App;
And of course move SignUp component to standalone file to keep the project clean and well structured.

Categories