I'm very new to React and I'm trying to write an app that will automatically display a Login page on startup, and when the user successfully logs in the parent component updates its state to render the Dashboard component instead of Login.
What I am finding is that when the user logs in, it updates the parent components state correctly and displays the Dashboard component for a second but then its state changes again and re-renders the Login component. I think something is causing the state to reset, but I'm not sure what.
User.js:
class User extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
isLoggedIn: false
}
this.updateUser = this.updateUser.bind(this);
}
updateUser(newEmail) {
console.log(`Entered User.updateUser, email: ${newEmail}`);
this.setState({
email: newEmail,
isLoggedIn: false
});
}
render() {
if (this.state.isLoggedIn) {
return <Dashboard/>
}
return <Login updateTheUser={this.updateUser}/>
}
}
export default User;
Login.js:
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
};
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleResponse = this.handleResponse.bind(this);
}
handleEmailChange(event) {
this.setState({email: event.target.value})
}
handlePasswordChange(event) {
this.setState({password: event.target.value})
}
handleResponse(res) {
if (res.ok) {
alert('Login Successful!');
this.props.updateTheUser(this.state.email);
}
else if (res.status === 401) {
alert('Wrong Username or Password');
}
}
async sendLoginRequest(data) {
await fetch('http://localhost:8000/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: data,
})
.then(this.handleResponse)
.catch(function(error) {
alert('Server error, please try again.');
console.error(error);
});
}
handleSubmit(event) {
const data = `{"email": "${this.state.email}", "password": "${this.state.password}"}`
this.sendLoginRequest(data);
}
render () {
return (
<div id="container" className="col-md-12" align="center">
<div id="vertical-center-div"className="col-sm-4 card bg-light">
<Form onSubmit={this.handleSubmit}>
<Form.Label className="display-4 text-secondary">Login</Form.Label>
<Form.Group controlId="formBasicEmail">
<Form.Control type="email" value={this.state.email} onChange={this.handleEmailChange} placeholder="Email" required/>
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Control type="password" value={this.state.password} onChange={this.handlePasswordChange} placeholder="Password" required/>
</Form.Group>
<Form.Group controlId="formBasicCheckbox">
<Form.Check type="checkbox" label="Remember me" />
</Form.Group>
<Button id="submitButton" variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
</div>
)
}
}
export default Login;
Dashboard.js:
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
expenses: [],
incomings: []
}
}
render () {
return (
<>
<p className="display-4">Dashboard</p>
</>
)
}
}
export default Dashboard;
It seems, that in Login.js you need to prevent default submit behaviour with event.preventDefault()
handleSubmit(event) {
event.preventDefault()
const data = `{"email": "${this.state.email}", "password": "${this.state.password}"}`
this.sendLoginRequest(data);
}
Related
I'm trying to build simple login form (data for authorization from API). So I have Slice for auth here is code :
auth.js
import { createSlice } from '#reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
initialState: {
isLoggedIn: false,
token: null,
role: null
},
reducers: {
logIn: (state) => {
state.isLoggedIn = true
},
role:(state, action) =>{
state.role = action.payload
},
token:(state, action) =>{
state.token = action.payload
},
logOut: (state) => {
state.isLoggedIn = false
},
},
})
export default authSlice.reducer;
export const { logIn, logOut, role, token } = authSlice.actions
authService.js :
import axios from 'axios';
import { api } from '../api/index'
export function authenticateUser(username, password) {
axios
.post(api + 'login', {username, password})
.then(res => {
console.log(res.headers.authorization)
})
}
LoginForm.js
import React, { Component } from 'react';
import { Form, Col, Button } from 'react-bootstrap';
import { IoMdLogIn } from "react-icons/all";
import { authenticateUser } from '../services/authService'
export default class LoginForm extends Component{
constructor(props) {
super(props);
this.state = this.initialState;
this.credentialsChange = this.credentialsChange.bind(this);
this.userLogin= this.userLogin.bind(this);
}
initialState = {
username: '', password: ''
}
userLogin = (e) => {
e.preventDefault();
authenticateUser(this.state.username, this.state.password);
this.setState( () => this.initialState)
}
credentialsChange = e => {
this.setState({
[e.target.name]:e.target.value
});
}
render(){
const {username, password} = this.state;
return(
<Form onSubmit={this.userLogin} id="loginFormId">
<Form.Row>
<Form.Group as={Col} controlId="formGridCountry">
<Form.Label>Username</Form.Label>
<Form.Control required autoComplete="off"
type="text" name="username"
value={username}
onChange={this.credentialsChange}
className={"bg-light"}
placeholder="Username" />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId="formGridZipCode">
<Form.Label>Password</Form.Label>
<Form.Control required autoComplete="off"
type="password" name="password"
value={password}
onChange={this.credentialsChange}
className={"bg-light"}
placeholder="Password" />
</Form.Group>
</Form.Row>
<Button type="submit" variant="success">
<IoMdLogIn />
</Button>
</Form>
);
}
}
What I'm trying to reach is : I want to update state isLoggedIn : true after calling function authenticateUser.
I've tried to use const dispatch = useDispatch() and then calling dispatch(logIn()) but it's throwing error.
Where should I call dispatcher to update state?
You need to call the dispatcher in your AuthService.js in the api response.
Check the response, if it is ok, store it. If your redux is well implemented, it will work.
axios
.post(api + 'login', {username, password})
.then(res => {
console.log(res.headers.authorization)
//Call it here, or create a function and call it here.
})
}
If it doesn't work, please share the error with us
I'm doing a login page with JWT and trying to redirect the page once log in to the home using this.props.history.push("/") but I get this typeError: Cannot read property 'push'of undefined. I don't know what I am doing wrong.
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "cihemzine#gmail.com",
password: "test",
};
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
login = () => {
const { email, password} = this.state;
axios("/users/login", {
method: "POST",
data: {
email,
password
},
})
.then((response) => {
localStorage.setItem('token', response.data.token);
console.log(response.data);
this.props.history.push("/");
})
.catch((error) => {
console.log(error);
});
};
render() {
return (
<div>
<div className="container">
<input value={this.state.email} onChange={this.handleChange} className="form-control mb-2 mt-4"name="email" type="text"placeholder="Your email" />
<input value={this.state.password} onChange={this.handleChange} className="form-control mb-2"name="password" type="password" placeholder="Your password" /><br></br>
<button onClick={this.login}className="text-center btn btn-light">LOGIN</button>
</div>
</div>
)
}
}
export default Login;
If you have nothing like 'history, match etc' in your props you need to check that you use your login component as Route
<Route path ='' component={Login} />
If you don't use Login in such way and can't for some reasons.
You can use HOC or hook for you component to add history to your component.
import { useHistory } from "react-router-dom"
or
import { withRouter } from "react-router";
I have exported multiple variables, but the method I'm using for storing this one does not seem to work for some reason. I have login page, which stores the correct value into "ID" as shown below
import AuthService from './AuthService';
let ID = "";
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.Auth = new AuthService();
}
handleFormSubmit(e){
e.preventDefault();
this.Auth.login(this.state.username,this.state.password)
.then(res =>{
if(this.Auth.state.isA)
this.props.history.push('/AdminApp');
else if(this.Auth.state.isA === 0 && this.Auth.state.sub === 0)
{
ID = this.Auth.state.userID;
console.log(ID) // This prints the right value
this.props.history.push('/SDForm')
}
})
.catch(err =>{
alert(err);
})
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
render() {
return (
<Container>
<Col className="UCFLogo"><img src={logo} /></Col>
<Form className="LoginForm">
<Col>
<h1 className="mainTitles">Senior Design Project Selection</h1>
<h3 className="subTitle">Sign In</h3>
</Col>
<Col>
<FormGroup className="LoginPad">
<Label className="subTitle">Knights Email</Label>
<Input className="LoginInfo" type="text" name="username" id="username" onChange={this.handleChange.bind(this)} value={this.state.username} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label className="subTitle" for="password">Password</Label>
<Input className="LoginInfo" type="password" name="password" id="password" onChange={this.handleChange.bind(this)} value={this.state.password} />
</FormGroup>
</Col>
<Button className="subTitle" onClick={this.handleFormSubmit}>Submit</Button>
</Form>
</Container>
);
}
}
export default LoginPage;
export {ID};
Then, I need to load that ID from login into my state in my form.js file (below) in order to return it to the json upon submit, I'm just attempting to print the ID to the console until I know that I am getting the right value, and for the sake of length, I cut most of the code out, but I get this in the console
ƒ LoginPage(props) {
var _this;
Object(C_csform_master_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_1__["default"])(this, LoginPage);
_this = Object(C_cs…
form.js
import ID from './LoginPage';
const Auth = new AuthService();
class SDForm extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: "",
ID: "",
}
this.Auth = new AuthService();
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
printToConsole = () => {
console.log(ID)
}
render() {
return (
<Container>
<Form className="SDForm">
// Form stuff
<Col className="subTitle">
<Button onClick={this.printToConsole}>Submit</Button>
</Col>
</Form>
</Container>
);
}
}
export default withAuth(SDForm);
This is not the proper way of passing information between components in React. In most cases, the best way to do it would be putting the value of ID in the Redux store or getting the ID value to them store it on a state and passing the ID state as a prop to the SDForm component, as shown next:
import SDForm from './SDForm.js'
And them (once you get your ID value and you store it on a state):
const { ID } = this.state;
And then in the <SDForm /> you can use ID prop as you see fit.
<SDForm id={ID} />
In the code, it reaches the isRegistered function, I know this as I have it console.log state. The state of registered is equal to true. So therefore based on the code it should redirect to /login but it is not.
import React from 'react'
import "./Register.css";
import {BrowserRouter as Route, Redirect, Link} from 'react-router-dom'
const initialUser = {
username: "",
email: "",
password: "",
password2: "",
name: ""
}
class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
user: initialUser,
registered: ''
};
}
onUsernameChange = event => {
this.setState({ username: event.target.value });
};
onNameChange = event => {
this.setState({ name: event.target.value });
};
onEmailChange = event => {
this.setState({ email: event.target.value });
};
onPasswordChange = event => {
this.setState({ password: event.target.value });
};
onPassword2Change = event => {
this.setState({ password2: event.target.value });
};
isRegistered() {
const { registered } = this.state;
console.log(registered, 'here', this.state)
if (registered) {
return (
<Redirect to='/login' />
)
}
}
onRegister = () => {
fetch("http://localhost:3000/register", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: this.state.name,
email: this.state.email,
username: this.state.username,
password: this.state.password,
password2: this.state.password2
})
})
.then(res => res.json())
.then(data => {
console.log(data.isRegistered);
if (data.isRegistered) {
this.setState({registered: true})
this.isRegistered();
}
})
};
render() {
return <div className="login-page">
<div className="form">
<form className="login-form">
<input type="text" placeholder="name" onChange={this.onNameChange} />
<input type="text" placeholder="username" onChange={this.onUsernameChange} />
<input type="text" placeholder="email" onChange={this.onEmailChange} />
<input type="password" placeholder="password" onChange={this.onPasswordChange} />
<input type="password" placeholder="confirm password" onChange={this.onPassword2Change} />
<button className="bluecolor" onClick={this.onRegister}>
Register
</button>
<p className="message">
Have an account? Login
</p>
</form>
</div>
</div>;
}
}
export default Register;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
It reaches all the way to the if statement in isRegistered(). So I assume it is the redirect component that is wrong but I cannot figure it out for the life of me.
//UPDATE
This is now what I have in it
import React from 'react'
import "./Register.css";
import {BrowserRouter as Route, Redirect, withRouter} from 'react-router-dom'
const initialUser = {
username: "",
email: "",
password: "",
password2: "",
name: ""
}
class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
user: initialUser,
registered: ''
};
}
onUsernameChange = event => {
this.setState({ username: event.target.value });
};
onNameChange = event => {
this.setState({ name: event.target.value });
};
onEmailChange = event => {
this.setState({ email: event.target.value });
};
onPasswordChange = event => {
this.setState({ password: event.target.value });
};
onPassword2Change = event => {
this.setState({ password2: event.target.value });
};
isRegistered() {
const { registered } = this.state;
console.log(registered, 'here', this.state)
if (registered) {
this.props.history.push('/login')
}
}
onRegister = () => {
fetch("http://localhost:3000/register", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: this.state.name,
email: this.state.email,
username: this.state.username,
password: this.state.password,
password2: this.state.password2
})
})
.then(res => res.json())
.then(data => {
console.log(data.isRegistered);
if (data.isRegistered) {
this.setState({registered: true})
this.isRegistered();
}
})
};
render() {
return <div className="login-page">
<div className="form">
<form className="login-form">
<input type="text" placeholder="name" onChange={this.onNameChange} />
<input type="text" placeholder="username" onChange={this.onUsernameChange} />
<input type="text" placeholder="email" onChange={this.onEmailChange} />
<input type="password" placeholder="password" onChange={this.onPasswordChange} />
<input type="password" placeholder="confirm password" onChange={this.onPassword2Change} />
<button className="bluecolor" onClick={this.onRegister}>
Register
</button>
<p className="message">
Have an account? Login
</p>
</form>
</div>
</div>;
}
}
export default withRouter(Register);
And this is the main App.js
import React, { Component } from 'react';
import RegisterFull from "./Components/Register/RegisterFull";
import LoginFull from "./Components/Login/LoginFull";
import HomeFull from "./Components/Home/HomeFull";
import FullContact from "./Components/Contact/FullContact";
import './App.css';
import './Components/flex.css'
import {BrowserRouter as Router, Route} from "react-router-dom";
class App extends Component {
constructor(props) {
super(props);
this.state = {
signedIn: false
}
}
loginUser = () => {
this.setState({signedIn: true})
}
render() {
return (
<Router>
<div>
<Route exact={true} path='/' component={HomeFull}/>
<Route path='/contact' component={FullContact} />
<Route path='/login' component={LoginFull} />
<Route path='/register' component={RegisterFull} />
<Route path='/about' component={HomeFull} />
</div>
</Router>
)
}
}
export default App;
You cannot return Redirect element in render like this by invoking another method in component lifecycles or methods.
You need to wrap your component with withRouter HOC which provides history props.
import {BrowserRouter as Route,, withRouter, Redirect, Link} from 'react-router-dom'
export default withRouter(Register);
and if you want to navigate programmatically :
isRegistered = () => {
const { registered } = this.state;
console.log(registered, 'here', this.state)
if (registered) {
this.props.history.push('/login)
}
}
Some improvements in your code you could make. Here is my suggestion. you don't need to bind anything. You are using latest React. How I know? You are doing onUsernameChange = evnet => {} not onUsernameChange(event){}. Overall #Sakhi Mansoor is right.
import React from "react";
import "./Register.css";
import {
BrowserRouter as Route,
withRouter,
Redirect,
Link
} from "react-router-dom";
const initialUser = {
username: "",
email: "",
password: "",
password2: "",
name: ""
};
class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
user: initialUser,
registered: ""
};
}
handleChange = event => {
this.setState({
user: {
...this.state.user,
[event.target.name]: event.target.value
}
});
};
isRegistered = () => {
const { registered } = this.state;
console.log(registered, "here", this.state);
if (registered) {
this.props.history.push("/login");
}
};
onRegister = event => {
event.preventDefault();
fetch("http://localhost:3000/register", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(this.state.user)
})
.then(res => res.json())
.then(data => {
console.log(data.isRegistered);
if (data.isRegistered) {
this.setState({ registered: true });
this.isRegistered();
}
});
};
render() {
return (
<div className="login-page">
<div className="form">
<form className="login-form" onSubmit={this.onRegister}>
<input
type="text"
placeholder="name"
name="name"
value={this.state.user.name}
onChange={this.handleChange}
/>
<input
type="text"
placeholder="username"
name="username"
value={this.state.user.username}
onChange={this.handleChange}
/>
<input
type="text"
placeholder="email"
name="email"
value={this.state.user.email}
onChange={this.handleChange}
/>
<input
type="password"
placeholder="password"
name="password"
value={this.state.user.password}
onChange={this.handleChange}
/>
<input
type="password"
placeholder="confirm password"
name="password2"
value={this.state.user.password2}
onChange={this.handleChange}
/>
<button className="bluecolor" type="submit">
Register
</button>
<p className="message">
Have an account? Login
</p>
</form>
</div>
</div>
);
}
}
export default withRouter(Register);
Here is a simple example of how to redirect on a button click.Hope this might help you
import React, { Component } from "react";
import { BrowserRouter,Route,Switch } from 'react-router-dom'
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Sample}/>
<Route path="/test" exact render={()=>(<p>Test</p>)}/>
</Switch>
</BrowserRouter>
)
}
}
class Sample extends React.Component {
constructor(props) {
super(props)
this.Click = this.Click.bind(this) // you need to bind the method
}
Click() {
this.props.history.push('/test');
}
render() {
return(
<button onClick={this.Click}>Click</button>
)
}
}
export default App;
I'm trying to implement authentication on my project, as title says it registers an user but actions are not dispatched. I have almost the same action for fetching data, it works, dispatches the actions. is the function:
export const signIn = data => dispatch => {
dispatch({
type: SIGN_UP
})
fetch(API_URL+'/register', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(message => dispatch({
type: SIGN_UP_SUCCESS, payload: message
}))
.catch(error => dispatch({
type: SIGN_UP_FAILED, payload: error
}))
}
Reducer:
export const authReducer = (state = initialState, action) => {
switch(action.type) {
case SIGN_UP:
return {
...state,
loading: true
}
case SIGN_UP_SUCCESS:
return {
...state,
loading: false,
message: action.payload
}
case SIGN_UP_FAILED:
return {
...state,
loading: false,
error: action.payload
}
default:
return state
}
}
connect method:
export default connect(null, { signIn })(RegisterForm);
Register Form component code(just to satisfy Stackoverflow's wishes):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Form, Button, Message, Field } from 'semantic-ui-react';
import validator from 'email-validator';
import { signUp } from '../../actions/authActions';
class RegisterForm extends React.Component {
constructor(props) {
super(props)
this.state = {
data: {
username: '',
name: '',
email: '',
password: '',
city: '',
address: ''
},
errors: {}
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = e => {
this.setState({
...this.state,
data: { ...this.state.data, [e.target.name]: e.target.value}
})
}
handleSubmit = e => {
console.log(this.state.data)
e.preventDefault();
const errs = this.validate(this.state.data);
this.setState({
errors: errs
});
if(Object.keys(this.state.errors).length === 0) {
this.props.signUp(this.state.data)
}
}
validate = data => {
const errors = {};
if(!data.username) errors.username = 'Username is required';
if(!data.name) errors.name = 'Name is required';
if(!data.email) errors.email = 'Email is required';
if (!validator.validate(data.email)) errors.email = "Invalid email";
if(!data.password) errors.password = 'Password is required';
if(!data.city) errors.city = 'City is required';
if(!data.address) errors.address = 'Address is required'
return errors
}
render() {
const { errors, data } = this.state
return <Form onSubmit={this.handleSubmit}>
<Form.Field>
<label>Username</label>
<input
placeholder='Username'
name="username"
type="text"
onChange={this.handleChange}
value={this.state.data.username}
/>
</Form.Field>
{errors.username && <Message error header={errors.username}/>}
<Form.Field>
<label>Name</label>
<input
placeholder='Name'
name="name"
type="text"
onChange={this.handleChange}
value={this.state.data.name}
/>
</Form.Field>
{errors.name && <Message error header={errors.name}/>}
<Form.Field>
<label>Address</label>
<input
placeholder='Address'
name="address"
type="text"
onChange={this.handleChange}
value={this.state.data.address}
/>
</Form.Field>
{errors.address && <Message error header={errors.address}/>}
<Form.Field>
<label>City</label>
<input
placeholder='City'
name="city"
type="text"
onChange={this.handleChange}
value={this.state.data.city}
/>
</Form.Field>
{errors.city && <Message error header={errors.city}/>}
<Form.Field>
<label>Email</label>
<input
placeholder='Email'
name="email"
type="email"
onChange={this.handleChange}
value={this.state.data.email}
/>
</Form.Field>
{errors.email && <Message error header={errors.email}/>}
<Form.Field>
<label>Password</label>
<input
placeholder='Password'
name="password"
type="password"
onChange={this.handleChange}
value={this.state.data.password}
/>
</Form.Field>
{errors.password && <Message error header={errors.password}/>}
<Button type='submit'>Register</Button>
</Form>
}
}
export default connect(null, { signUp })(RegisterForm);
Your code seems to be fine, make sure your redux-devtools is implemented correctly.
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(thunk)) // [, rest of middlewares]
Did you use bindActionCreators inside your component? in handleSubmit you just called action without dispatching it