I implemented a simple Login Form in React with Redux following this tutorial: https://jslancer.com/blog/2017/04/27/a-simple-login-flow-with-react-and-redux/
Everything works, but when I add cookies I get the error:
Warning: Cannot update during an existing state transition (such as
within render). Render methods should be a pure function of props
and state.
and also:
Uncaught Invariant Violation: 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.
I did some debugging and if I remove onChange={e => this.setState({password: e.target.value})} from the inputs in the code below the error disappears.
Any ideas why the following code is not working?
import { connect } from 'react-redux';
import { withCookies } from 'react-cookie'
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
}
render() {
let {username, password} = this.state;
let { cookies, allCookies, isLoginPending, isLoginSuccess, loginError} = this.props;
cookies.set('username', 'Ross', { path: '/', secure: true, httpOnly: true});
console.log(cookies.get('username'));
return (
<form name="loginForm" onSubmit={this.onSubmit}>
<div className="form-group-collection">
<div className="form-group">
<label>Username:</label>
<input type="text" name="username" onChange={e => this.setState({username: e.target.value})} value={username}/>
</div>
<div className="form-group">
<label>Password:</label>
<input type="password" name="password" onChange={e => this.setState({password: e.target.value})} value={password}/>
</div>
</div>
</form>
)
}
}
const mapStateToProps = (state) => {
return {
isLoginPending: state.isLoginPending,
isLoginSuccess: state.isLoginSuccess,
loginError: state.loginError,
};
}
export default withCookies(connect(mapStateToProps, null)(LoginForm));```
My guess is that because your component it connected to the cookies HoC and then you are calling cookies.set in the render method, it is updating itself every time, creating an infinite loop. Please try moving cookies.set to componentDidMount.
Related
I wish to share this code just in case someone might need to solve such a problem of filtering unwanted characters when doing forms in react. For extras, my code shows how to pass props to components inside Route. For simplicity, I have focused on only these two inputs and omitted other stuff such as the submit button and css data for styling those classNames and ids.
import React, { Component } from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import SignupForm from "./components/SignupForm";
class App extends Component {
constructor() {
super();
this.state = {
firstName: "",
lastName: "",
};
//Binding this to the functions used as they wouldn't just work if not bound
this.changeFirstName = this.changeFirstName.bind(this);
this.changeLastName = this.changeLastName.bind(this);
this.lettersOnly = this.lettersOnly.bind(this);
}
changeFirstName(e) {
this.setState({ firstName: e.target.value });
}
changeLastName(e) {
this.setState({ lastName: e.target.value });
}
// Error handler functions
lettersOnly(nameInput) {//Replacing all characters except a-z by nothing with this function
let regex = /[^a-z]/gi;
nameInput.target.value = nameInput.target.value.replace(regex, "");
}
render() {
return (
<Router>
<div className="App">
<Route
exact
path="/"
comp={SignupForm}
render={() => (
<SignupForm
//SignupForm submit props
changeFirstNameHandler={this.changeFirstName}
firstNameValue={this.state.firstName}
changeLastNameHandler={this.changeLastName}
lastNameValue={this.state.lastName}
// Error handlers
nameCharacterFilter={this.lettersOnly}
/>
)}
/>
)}
/>
</div>
</Router>
);
}
}
export default App;
Below is the signup form, which is the child component in this aspect, and also a function component as opposed to its parent component:
import React from "react";
export default function SignupForm(props) {
return (
<div className="container" id="signupForm">
<h1>Signup Form</h1>
<div className="form-div">
<form>
<input
type="text"
placeholder="First Name"
onChange={props.changeFirstNameHandler}
value={props.firstNameValue}
onKeyUp={props.nameCharacterFilter}
className="form-control formgroup"
/>
<input
type="text"
placeholder="Last Name"
onChange={props.changeLastNameHandler}
value={props.lastNameValue}
onKeyUp={props.nameCharacterFilter}
className="form-control formgroup"
/>
</form>
</div>
</div>
);
}
NB: Welcome to improve this code, if you feel the need!
I think you can improve you're code with this changes:
Use the regex directly in the onChange event
Use only one method to update the values
Here is an example of what I mean: https://codesandbox.io/s/react-playground-forked-vreku?fontsize=14&hidenavigation=1&theme=dark
Regards!
Okay here is an improved code and much more cleaner. However, I have just omitted the React Router part to focus on functionality of the state and functions in this case.
I also want the user to see when they type an unwanted character that it actually typed but then just deleted on key up so I have created an independent function justLettersAndHyphen(nameField) from changeValue(event) that is triggered by onKeyUp.
import React from "react";
import SignupForm from "./SignupForm";
class App extends React.Component {
constructor() {
super();
this.state = {
firstName: "",
lastName: ""
};
this.changeValue = this.changeValue.bind(this);
this.justLettersAndHyphen = this.justLettersAndHyphen.bind(this);
}
changeValue(event) {
this.setState({
[event.target.name]: event.target.value,
});
}
// Error handler functions
justLettersAndHyphen(nameField) {
let regex = /[^a-z-]/gi;
nameField.target.value = nameField.target.value.replace(regex, "");
}
render() {
return (
<SignupForm
firstNameValue={this.state.firstName}
lastNameValue={this.state.lastName}
changeValueHandler={this.changeValue}
nameCharacterFilter={this.justLettersAndHyphen}
/>
);
}
}
export default App;
Child component edited with name property added.
import React from "react";
export default function SignupForm(props) {
return (
<div className="container" id="signupForm">
<h1>Signup Form</h1>
<div className="form-div">
<form>
<input
name="firstName"
type="text"
placeholder="First Name"
onChange={props.changeValueHandler}
onKeyUp={props.nameCharacterFilter}
value={props.firstNameValue}
/>
<input
name="lastName"
type="text"
placeholder="Last Name"
onChange={props.changeValueHandler}
onKeyUp={props.nameCharacterFilter}
value={props.lastNameValue}
/>
</form>
</div>
</div>
);
}
I'm trying to redirect to my user-dashboard page after the user is successfully logged in.
I've already tried using push('/dashboard') but even if the credentials were false it redirect to the dashboard page any help please !
So I want when I submit the email and the password correct to login, it will be redirected to the dashboard
How can i add a condition to test if the user is really authenticated ?
import React, { Component } from "react";
import "./Authentification.css";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { loginUser } from "../actions/authActions";
import { Link } from "react-router-dom";
import { Button, TextField } from "#material-ui/core";
import axios from "axios";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
errors: {}
};
this.onSubmit = this.onSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value});
}
onSubmit = e => {e.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password
};
this.props.loginUser(userData);
this.props.history.push("/dashboard");
};
render() {
return (
<div className="hero">
<div className="form-box-login">
<img src={logo} className="image"></img>
<h1 className="styledh1">LOGIN</h1>
<form className="input-group">
<form onSubmit={this.onSubmit}>
<input type="email"
name="email"
value={this.state.email}
onChange={this.handleChange}
placeholder="Email" className="input-field"/>
<input type="password"
name="password"
label="Password"
value={this.state.password}
onChange={this.handleChange}
placeholder="Password" className="input-field"
/>
<p className="forgot-password">Forgot your password?</p>
<button type="submit" className="submit-btn">LOGIN</button>
</form>
</form>
</div>
</div>
);
}
}
Login.propTypes = {
loginUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated,
error: state.error
});
export default connect(mapStateToProps, {loginUser})(Login);
The function responsible for authorization (loginUser) is the place where you get your response from server and manage your React app's behaviour. Since it is at Login's parent, you either have to pass some notification down to Login whenever user gets authenticated, thru props OR (and it's the preferable way to do it, necessary if it's not a very small app) set the redux store's state accordingly (something like authenticated: true).
Now, when you have your authenticated prop in Login component (passed either by parent locally or by redux globally), you can render conditionally:
render() {
if (!this.props.authenticated)
return (<Redirect to='/dashboard'/>);
else
return (
//your Login jsx
)
}
I am new to React and I try to learn it from the recent official documentation where 'componentWillReceiveProps' no longer in lifecycle.
I have a Children component for NEW Product to post :
import React, { Component } from "react";
export default class NewProduct extends Component {
state = {
title : "",
price : 0
}
handleChange =(e)=>{
this.setState({ [e.target.name]: e.target.value });
}
handleSubmit =()=>{
let formData = {
title : this.state.title,
price : this.state.price
}
console.log(formData)
// axios here with formData
}
render() {
return (
<div>
<input type="text" name="title" value={this.state.title}
onChange={this.handleChange}
/>
<input type="text" name="price" value={this.state.price}
onChange={this.handleChange}
/>
<button onClick={this.handleSubmit}>Submit</button>
</div>
)
}
}
This works OK for creating product on database.
And For EDIT Product, the Parent component load data from database using axios and pass it as props to EditProduct Children. <EditProduct productData={this.state.productData} />
Edit form :
import React, { Component } from "react";
export default class EditProduct extends Component {
state = {
title : "",
price : 0
}
handleChange =(e)=>{
// how to handle change........???
}
handleSubmit =()=>{
let formData = {
title : this.state.title,
price : this.state.price
}
console.log(formData)
// axios here with formData
}
render() {
const data = this.props.productData;
return (
<div>
<input type="text" name="title" value={data.title || ""}
onChange={this.handleChange}
/>
<input type="text" name="price" value={data.price || ""}
onChange={this.handleChange}
/>
<button onClick={this.handleSubmit}>Submit</button>
</div>
)
}
}
My problem is I can not change the state by using handleChange since the value on each inputs no more refer to state.
I have tried to solve this problem by using componentWillReceiveProps and change the states by setState. But this method is no more mentioned inside lifecycle, so I need other solution.
Other solution I have tried was using context API where parent store data to state and my EditProduct component consume and modify that states.
My question is : What is the best valid ways to replace componentWillReceiveProps?
Note : Please do not suggest Redux.
For some weird reason by action creator is not being called properly when I try to trigger it from my component. I am trying to return another function to dispatch to my reducers as is the standard with redux-thunk. However, I am unable to return anything.
The component is triggering the 'signinUser' action function, the console.log displays the email and password values (line 7 of action file), and then it skips the return code (line 8 and onward) in action file.
Is there a silly mistake that I am making? All my other components work fine. I am using react version. 15.1
ACTION
import axios from 'axios';
import {browserHistory} from 'react-router';
import {AUTH_USER, UNAUTH_USER, AUTH_ERROR, ROOT_AUTH_URL} from '../constants/auth.constants';
export function signinUser({email, password}) {
console.log('email and password in action', email, password);
---- ^^^ THIS CONSOLE PART WORKS.
**---- THE RETURN FUNCTION below is not working... BELOW CODE IS NOT RENDERING ANYTHING.**-------
return dispatch => {
console.log('pass the return'); ------> does not work :-(
// Submit email/password to the server
axios.post(`${ROOT_AUTH_URL}signin`, {email, password})
.then(response => {
console.log('response', response);
// - Update state to indicate user is authenticated: flag will turn to "true"
dispatch({type: AUTH_USER});
// - Save the JWT token in local storage
localStorage.setItem('token', response.data.token);
// - redirect to the route '/feature'
browserHistory.push('/');
})
.catch(() => {
// if request is bad...
// - Show an error to the user
dispatch(authError('Bad Login Info!'));
});
}
}
**---- ABOVE RETURN STATEMENT DOES NOT WORK **-----------------
COMPONENT
import React, {Component, PropTypes} from 'react';
import {reduxForm, Field} from 'redux-form';
import {signinUser} from '../../actions/auth.actions';
class Signin extends Component {
// used to take supplied inputs and check auth
handleFormSubmit({email, password}) {
// Need something to log user in
console.log('test', email, password);
signinUser({email, password});
**^^^^^ THIS PART is TRIGERRING THE ACTION**
}
renderAlert() {
if (this.props.errorMessage) {
return (
<div className="alert alert-danger">
<strong>Oops!</strong> {this.props.errorMessage}
</div>
);
}
}
render() {
// handleSubmit is a built in redux-form helper to bind ui to values
const {handleSubmit} = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<fieldset className="form-group">
<label>Email:</label>
<Field name="email" component="input" type="text" required className="form-control"/>
</fieldset>
<fieldset className="form-group">
<label>Password:</label>
<Field name="password" component="input" type="password" required className="form-control"/>
</fieldset>
{this.renderAlert()}
<button action="submit" className="btn btn-primary">Sign in</button>
</form>
);
}
}
Signin.propTypes = {
signinUser: PropTypes.func,
errorMessage: PropTypes.string,
handleSubmit: PropTypes.func
};
function mapStateToProps(state) {
return {errorMessage: state.auth.error};
}
export default reduxForm({
form: 'signin'
}, mapStateToProps, signinUser)(Signin);
Generally when I have a disconnect like this, and when I say generally I mean 200% of the time, it is because something is misspelled or missing. I notice that in your action:
UTH_USER, ROOT_AUTH_URL
are in use, but
UNAUTH_USER, AUTH_ERROR
are not.
In the situation of unauth or an error I am not sure that this would do anything at all. Check the network tab of the browser devtools to look for an error, console.log everywhere else and check that spelling.
I hope you find it!
Following my first React tutorial. My code seems to be exactly like the tutorial, but my input doesn't reset within a form component. The first time I submit, everything works fine, but the state holds onto the first value. Even when calling setState with a console.log in a callback, it seems like setState doesn't even fire. this is bound in my constructor function.
import React, { Component } from 'react';
import TenantActions from '../actions/TenantActions';
export default class AddTenantForm extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
}
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(event) {
event.preventDefault();
console.log('1. On Submit click, sending addNewTenant action w/', this.state);
TenantActions.addNewTenant(this.state);
this.setState = ({ name: '' });
}
render() {
return (
<form>
<div className="form-group">
<input type="text"
className="form-control"
id="tenantName"
placeholder="Bob Smithers"
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
</div>
<button className="btn btn-default"
onClick={this.onSubmit}
>Submit</button>
</form>
)
}
}
this.setState is a function. You have a typo (= extra) in the function onSubmit.
Replace this.setState = ({...}) with this.setState({name: ''})
More about setState