I am trying to set state via this.setState() of username and password when user types a value in username and password field. I am using onChange event type
ISSUE: the state is not changing. I log this.state.data.username in the render().
import React, { Component } from "react";
import { Form, Button } from "react-bootstrap";
import { Link } from "react-router-dom";
var Joi = require("joi-browser");
class Login extends Component {
state = {
data: { username: "a", password: "b " },
errors: {
email: "ddsfds",
password: "aaaa"
}
};
schema = {
username: Joi.string()
.min(0)
.required()
.label("Username"),
password: Joi.string()
.required()
.label("Password")
};
handleSubmit = event => {
event.preventDefault();
console.log("submited.", event.target);
const { data } = this.state;
const { err } = Joi.validate(data, this.schema);
if (err) {
console.log("error is true", err);
} else {
console.log("not true");
}
};
handleEmailOnChange = event => {
const inputUsername = event.target.value;
console.log("input is...", inputUsername);
this.setState({ username: inputUsername });
};
handlePassword = event => {
const passwordInput = event.target.value;
this.setState({ password: passwordInput });
};
render() {
console.log("username ", this.state.data.username);
return (
<div id="form-wrapper">
<Form>
<Form.Group controlId="formBasicEmail">
<h4>Sign In</h4>
<Form.Control
type="email"
placeholder="Enter email"
onChange={this.handleEmailOnChange}
/>
{/* <span>{this.state.errors.username} </span> */}
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Control
type="password"
placeholder="Password"
onChange={this.handlePassword}
/>
</Form.Group>
<div id="register-wrapper">
<Link to="/register" type="button" className="btn btn-warning">
Register Account
</Link>
<Button
variant="primary"
className="m-2"
type="submit"
onClick={this.handleSubmit}
>
Submit
</Button>
</div>
</Form>
</div>
);
}
}
export default Login;
You aren't updating the state correctly or not using it correctly. The state in your constructor has data object with username and password
handleEmailOnChange = event => {
const inputUsername = event.target.value;
console.log("input is...", inputUsername);
this.setState(prev => ({data: {...prev.data, username: inputUsername } }));
};
handlePassword = event => {
const passwordInput = event.target.value;
this.setState(prev => ({data: {...prev.data, password: passwordInput } }));
};
The state you are changing is this.state.username, the one you console is this.state.data.username.
To set data in your state, use:
this.setState(prevState => ({
data: {
username: inputUsername,
...prevState.data
}
})
Related
I want to create a login form with array data in User.js file (without any backend)
My User.js file looks like this:
const users = [
{
username: 'admin1',
password: 'admin1#1'
},
{
username:'admin2',
password:'admin2#2'
}
];
and my Login.js looks something like this:
import React, { Component, Fragment } from 'react';
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
userName: "",
password: ""
};
}
changeInputValue(e) {
this.setState({
[e.target.name]: e.target.value
});
}
validationForm() {
let returnData = {
error : false,
msg: ''
}
const {password} = this.state
//Check password
if(password.length < 8) {
returnData = {
error: true,
msg: 'Password must be more than 8 characters'
}
}
return returnData;
}
submitForm(e) {
e.preventDefault();
const validation = this.validationForm()
var username = e.target.elements.username.value;
var password = e.target.elements.password.value;
if (validation.error) {
alert(validation.msg)
}else if(username === 'admin2' && password === 'admin1#1') {
alert("Login successful");
}else {
alert("Wrong password or username");
}
}
render() {
return (
<div className="container" style={{ paddingTop: "5%" }}>
<form
onSubmit={e => {
this.submitForm(e);
}}
>
<div className="form-group">
<input
type="text"
className="form-control"
name="username"
placeholder="Username"
onChange={e => this.changeInputValue(e)}
/>
</div>
<div className="form-group">
<input
type="password"
className="form-control"
name="password"
placeholder="Password"
onChange={e => this.changeInputValue(e)}
/>
</div>
<button value="submit" className="btn btn-primary" onClick={this.postDetails}>
Submit
</button>
</form>
</div>
);
}
}
export default Login;
Above, I don't know how to check username and password from the passed array. Please show an instance of how it is done. And after successful login, how do I switch to another page?Sorry, I'm new to ReactJS so I'm a bit confused, hope you can help.
Import the data from user.js after u exported it from there.
export const UsersData = [
{
username: 'admin1',
password: 'admin1#1'
},
{
username:'admin2',
password:'admin2#2'
}
];
//Login.js
import UsersData from './user.js'
Now do array operation on this array of objects. Now you can convert the data entered by the user to a similar format provided by User.js.
ie: If the user entered username = alphabeta, password = 1234
submitForm(e) {
e.preventDefault();
const validation = this.validationForm()
var inputData = {
username : e.target.elements.username.value,
password : e.target.elements.password.value
};
if (validation.error) {
alert(validation.msg)
}else if(UsersData.findIndex(inputData)!==-1) {
alert("Login successful");
}else {
alert("Wrong password or username");
}
}
Here we uses an array operation called findIndex which returns -1 if it doesn't find the object in the array else it returns the index.
To know more about Array Opreration in JS : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
Not sure if I understand correstly your question but here is how you can check if the duo user and password are in your array :
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const users = [
{
username: "admin1",
password: "admin1#1",
},
{
username: "admin2",
password: "admin2#2",
},
];
const handleSubmit = (e) => {
e.preventDefault();
const toCompare = {
username: e.target.elements.username.value,
password: e.target.elements.password.value,
};
// Or just compare properties
if (users.some(u => JSON.stringify(u) === JSON.stringify(toCompare))) {
console.log('User exists in array');
} else {
console.log('User does not exist in array');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="username" name="username" />
<input type="text" placeholder="password" name="password" />
<button type="submit">Send</button>
</form>
);
};
render(<App />, document.getElementById('root'));
And here is the repro on Stackblitz
I have the following code:
import React, { Component } from 'react'
import axios from 'axios'
import Navbar from '../Navbar'
import { Avatar, TextField, Button, Container, CircularProgress } from '#material-ui/core'
import Alert from '#material-ui/lab/Alert'
class PrivateProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
id: null,
image: null,
pp: null,
username: 'AnonymousUser',
showSuccess: false
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleFileChange = this.handleFileChange.bind(this)
}
componentDidMount() {
axios.get('http://127.0.0.1:8000/users/profile')
.then(res => {
this.setState({
user: res.data,
id: res.data.id,
username: res.data.username,
pp: res.data.pp
})
})
.catch(err => console.log(err))
}
handleSubmit(e) {
e.preventDefault()
const fd = new FormData()
fd.append('pp', this.state.image)
fd.append('username', this.state.user.username)
fd.append('email', this.state.user.email)
fd.append('bio', this.state.user.bio)
const d = {
pp : this.state.image,
username : this.state.user.username,
email : this.state.user.email,
bio : this.state.user.bio
}
console.log('d', d)
console.log('fd', fd)
axios.put(`http://127.0.0.1:8000/users/profile/update/${this.state.id}/`, fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(res => {
this.setState({
user: res.data,
id: res.data.id,
pp: res.data.pp,
image: null,
username: res.data.username,
showSuccess: true
})
})
.catch(err => console.log(err))
}
handleChange(e) {
this.setState({
user: {
[e.target.name]: e.target.value
}
})
}
handleFileChange(e) {
this.setState({image: e.target.files[0]})
}
render() {
let message
let alert
if (this.state.user !== null) {
if (!this.state.user.bio) {
message = <h4>Please update your profile below.</h4>
}
if (this.state.showSuccess) {
alert = <Alert action={<Button onClick={() => this.setState({showSuccess: false})}>Close</Button>} severity='success'>Profile Successfully Updated</Alert>
}
return (
<div>
<Navbar />
<Container style={{background: '#f7f4e9'}}>
<div style={{height: '60px'}}></div>
<h2>Your Profile</h2>
<Avatar src={this.state.user.pp} alt={this.state.user.username} />
{message}
{alert}
<h4>Your data:</h4>
<form onSubmit={this.handleSubmit}>
<p>Profile Pic</p>
<input type="file" onChange={this.handleFileChange}/>
<br></br>
<br></br>
<TextField label='Username' name="username" onChange={this.handleChange} type="text" value={this.state.user.username} />
<br></br>
<br></br>
<TextField label='Email' name="email" onChange={this.handleChange} type="email" value={this.state.user.email} />
<br></br>
<br></br>
<TextField label='Bio' name="bio" onChange={this.handleChange} type="text" value={this.state.user.bio} />
<br></br>
<br></br>
<br></br>
<Button type="submit" value="submit">Update</Button>
</form>
</Container>
</div>
)
} else {
return <CircularProgress />
}
}
}
export default PrivateProfile
I get the error saying: Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Can someone help me fix it.
Since you're initializing state values with null and using it like value={this.state.user.username}, and update the state, you'll get such error:
Warning: A component is changing a controlled input of type text to be uncontrolled.
To control it's state, use it like:
value={this.state.user.username || ''}
As per my comment, you have issue here:
handleChange(e) {
this.setState({
user: {
[e.target.name]: e.target.value
}
})
}
The user state will always change on your any input changes, you will need like:
handleChange(e) {
this.setState({
user: {
...this.state.user,
[e.target.name]: e.target.value
}
})
}
I have set everything in Redux side and I see every action in Redux Devtool. It works all perfect. The problem occurs when I want to dispatch action in React Component. In login component I want to dispatch action, wait its response then depending on response redirect it to a page or show errors. Here are my codes:
userActions.js
import axios from "axios";
import {
LOGIN_USER,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAILED,
} from "./types";
const loginUserRequest = () => {
return {
type: LOGIN_USER,
};
};
const loginUserSuccess = (user) => {
return {
type: LOGIN_USER_SUCCESS,
payload: user,
};
};
const loginUserFailed = (error) => {
return {
type: LOGIN_USER_FAILED,
payload: error,
};
};
export const loginUser = (dataSubmitted) => {
return (dispatch) => {
dispatch(loginUserRequest());
axios
.post("/api/users/login", dataSubmitted)
.then((response) => {
dispatch(loginUserSuccess(response.data));
})
.catch((err) => {
dispatch(loginUserFailed(err));
});
};
};
userReducer.js:
import {
LOGIN_USER,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAILED,
} from "../actions/types";
const initialState = {
loading: false,
user: "",
error: "",
};
export default function (state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
return { ...state, loading: true };
case LOGIN_USER_SUCCESS:
return { ...state, loading: false, user: action.payload, error: "" };
case LOGIN_USER_FAILED:
return { ...state, loading: false, user: "", error: action.payload };
default:
return state;
}
}
The above codes works great and does the job. The problem is in following code where I am dispatching the async action. After I run the code I get this.props.userData as undefined.
Login.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { loginUser } from "../../actions/userActions";
export class Login extends Component {
state = {
email: "",
password: "",
errors: [],
};
displayErrors = (errors) =>
errors.map((error, i) => (
<div className="alert alert-danger" key={i}>
{error}
</div>
));
handleSubmit = (e) => {
e.preventDefault();
var { email, password } = this.state;
var errors = [];
if (email === "") {
errors.push("Email is required");
}
if (password === "") {
errors.push("Password is required");
}
this.setState({ errors: errors });
//Problem occurs here
this.props.dispatch(loginUser({ email, password }));
if (response.payload.success) {
sessionStorage.setItem("jwt", response.payload.token);
sessionStorage.setItem("userId", response.payload._id);
this.props.history.push("/");
} else {
errors.push("Username and/or Password is not correct");
this.setState({ errors: errors });
}
};
render() {
return (
<form className="form-signin" onSubmit={this.handleSubmit}>
<h1 className="h3 mb-3 font-weight-normal">Sign in</h1>
{this.state.errors.length > 0 && this.displayErrors(this.state.errors)}
<label for="inputEmail" className="sr-only">
Email address
</label>
<input
type="email"
id="inputEmail"
className="form-control"
placeholder="Email address"
value={this.state.email}
onChange={(e) => {
this.setState({ email: e.target.value });
}}
required
autoFocus
/>
<label for="inputPassword" className="sr-only">
Password
</label>
<input
type="password"
id="inputPassword"
className="form-control"
placeholder="Password"
value={this.state.password}
onChange={(e) => {
this.setState({ password: e.target.value });
}}
required
/>
<div className="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me" /> Remember me
</label>
</div>
<button
className="btn btn-lg btn-primary btn-block"
type="submit"
onClick={this.handleSubmit}
>
Sign in
</button>
<Link to="/register">Sign Up</Link>
</form>
);
}
}
function mapStateToProps(state) {
return {
userData: state.user,
};
}
export default connect(mapStateToProps)(Login);
You need to install connected-react-router to manipulate with a history inside redux:
import { push } from 'connected-react-router';
export const loginUser = (dataSubmitted) => {
return (dispatch) => {
dispatch(loginUserRequest());
axios
.post("/api/users/login", dataSubmitted)
.then((response) => {
dispatch(loginUserSuccess(response.data));
if (response.payload.success) {
sessionStorage.setItem("jwt", response.payload.token);
sessionStorage.setItem("userId", response.payload._id);
push("/");
} else {
errors.push("Username and/or Password is not correct");
}
})
.catch((err) => {
dispatch(loginUserFailed(err));
});
};
};
As the title says, I have a check in RegistrationForm for checking if user email exists in the database. The prop is userExists. Now, if it's true, that means User email exists, so the user should be shown a message that says- "User already exists." Now, if it's false, he should be successfully registered and redirected to login.
But right now, I cannot even register with any email. In the network tab, it returns a 200 and gives
the response:
{"message":"User with this email does not exist"}
Can someone show me the proper way to do it? I mean the check, and the message from the backend, through redux, and back to the component.
My code is jibberish.
RegistrationForm Component
import React, { Component } from "react";
import { registerUser, checkValidUser } from "../../actions/userActions";
import { connect } from "react-redux";
import validator from "validator";
import { Link } from "react-router-dom";
import { toastError } from "../../../utils/toastify";
class RegistrationForm extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
email: "",
password: "",
};
}
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
};
handleSubmit = async (event) => {
event.preventDefault();
const { username, email, password } = this.state;
const registrationData = {
username: this.state.username,
email: this.state.email,
password: this.state.password,
};
if (!username || !email || !password) {
return toastError("Credentials should not be empty");
}
if (username.length < 6) {
return toastError("Username should be greater than 6 characters.");
}
if (!validator.isEmail(email)) {
return toastError("Invalid email.");
}
if (password.length < 6) {
return toastError("Password must contain 6 characters.");
}
await this.props.dispatch(checkUserExists(email));
const userExists = this.props.userExists;
if (!userExists) {
this.props.dispatch(
registerUser(registrationData, () => {
this.props.history.push("/login");
})
);
} else {
toastError("User with this email already exisits"); // I'm not sure how to show the message if the user email already exists. I want to show the message from backend, but currently I'm just doing it manually
}
};
render() {
const isRegistrationInProgress = this.props.isRegistrationInProgress;
return (
<div>
<div className="field">
<p className="control has-icons-left has-icons-right">
<input
onChange={this.handleChange}
name="username"
value={this.state.username}
className="input"
type="text"
placeholder="Username"
/>
<span className="icon is-small is-left">
<i className="fas fa-user"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left has-icons-right">
<input
onChange={this.handleChange}
name="email"
value={this.state.email}
className="input"
type="email"
placeholder="Email"
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left">
<input
onChange={this.handleChange}
name="password"
value={this.state.password}
className="input"
type="password"
placeholder="Password"
/>
<span className="icon is-small is-left">
<i className="fas fa-lock"></i>
</span>
</p>
</div>
<div className="field">
<div className="control">
{isRegistrationInProgress ? (
<button className="button is-success is-loading">Sign Up</button>
) : (
<button onClick={this.handleSubmit} className="button is-success">
Sign up console.log("registrationData", registrationData)
</button>
)}
<Link to="/login">
<p className="has-text-danger">
Already have an account? Sign In
</p>
</Link>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isRegistrationInProgress: state.registration.isRegistrationInProgress,
userExists: state.registration.userExists,
};
};
export default connect(mapStateToProps)(RegistrationForm);
checkUserExists action
export const checkUserExists = (email) => {
return async (dispatch) => {
try {
const res = await axios.get(`${baseUrl}/users/checkUserExists/${email}`)
console.log("res=>", res)
if (res.data.message = "User with this email already exists") {
dispatch({
type: "CHECK_USER_EXISTS_SUCCESS",
})
}
} catch (err) {
console.log("error=>", err)
}
}
}
checkUserExists controller function
checkUserExists: async (req, res, next) => {
const { email } = req.params
try {
const user = await User.findOne({ email })
if (user) {
return res.status(200).json({ message: "User with this email already exists" })
} else {
return res.json({ message: "User with this email does not exist" })
}
} catch (error) {
return next(error)
}
}
registerUser action
export const registerUser = (registrationData, redirect) => {
return async (dispatch) => {
dispatch({ type: "REGISTRATION_STARTS" })
try {
const res = await axios.post(
`${baseUrl}/users/register`,
registrationData
)
dispatch({
type: "REGISTRATION_SUCCESS",
data: { user: res.data.user },
})
toastSuccess("Successfully registered")
redirect()
} catch (err) {
dispatch({
type: "REGISTRATION_ERROR",
data: { error: err },
})
}
}
}
registration reducer
const initialState = () => ({
isRegistrationInProgress: false,
isRegistered: false,
registrationError: null,
user: {},
userExists: false,
error: null,
});
const registration = (state = initialState, action) => {
switch (action.type) {
case "REGISTRATION_STARTS":
return {
...state,
isRegistrationInProgress: true,
registrationError: null,
};
case "REGISTRATION_SUCCESS":
return {
...state,
isRegistrationInProgress: false,
registrationError: null,
isRegistered: true,
user: action.data,
};
case "REGISTRATION_ERROR":
return {
...state,
isRegistrationInProgress: false,
registrationError: action.data.error,
isRegistered: false,
user: {},
};
case "CHECK_USER_EXISTS_SUCCESS":
return {
...state,
userExists: true,
error: null
};
default:
return state;
}
};
export default registration;
Hi i found an answer to this for a single field form... but what if we have a form with multiple field?
this is fine for disabling it if you have 1 field but it does not work when you want to disable it based on many fields:
getInitialState() {
return {email: ''}
},
handleChange(e) {
this.setState({email: e.target.value})
},
render() {
return <div>
<input name="email" value={this.state.email} onChange={this.handleChange}/>
<button type="button" disabled={!this.state.email}>Button</button>
</div>
}
})
Here is a basic setup for form validation:
getInitialState() {
return {
email: '',
text: '',
emailValid: false, // valid flags for each field
textValid: false,
submitDisabled: true // separate flag for submit
}
},
handleChangeEmail(e) { // separate handler for each field
let emailValid = e.target.value ? true : false; // basic email validation
let submitValid = this.state.textValid && emailvalid // validate total form
this.setState({
email: e.target.value
emailValid: emailValid,
submitDisabled: !submitValid
})
},
handleChangeText(e) { // separate handler for each field
let textValid = e.target.value ? true : false; // basic text validation
let submitValid = this.state.emailValid && textvalid // validate total form
this.setState({
text: '',
textValid: textValid,
submitDisabled: !submitValid
})
},
render() {
return <div>
<input name="email" value={this.state.email} onChange={this.handleChangeEmail}/>
<input name="text" value={this.state.text} onChange={this.handleChangeText}/>
<button type="button" disabled={this.state.submitDisabled}>Button</button>
</div>
}
})
In a more elaborate setup, you may want to put each input field in a separate component. And make the code more DRY (note the duplication in the change handlers).
There are also various solutions for react forms out there, like here.
I would take a little bit different way here...
Instead of setting submitDisabled in every single onChange handler I would hook into lifecycle method to listen to changes.
To be exact into componentWillUpdate(nextProps, nextState). This method is invoked before every change to component - either props change or state change. Here, you can validate your form data and set flag you need - all in one place.
Code example:
componentWillUpdate(nextProps, nextState) {
nextState.invalidData = !(nextState.email && nextState.password);
},
Full working fiddle https://jsfiddle.net/4emdsb28/
This is how I'd do it by only rendering the normal button element if and only if all input fields are filled where all the states for my input elements are true. Else, it will render a disabled button.
Below is an example incorporating the useState hook and creating a component SubmitButton with the if statement.
import React, { useState } from 'react';
export function App() {
const [firstname, setFirstname] = useState('');
const [lastname, setLastname] = useState('');
const [email, setEmail] = useState('');
function SubmitButton(){
if (firstname && lastname && email){
return <button type="button">Button</button>
} else {
return <button type="button" disabled>Button</button>
};
};
return (
<div>
<input value={email} onChange={ e => setEmail(e.target.value)}/>
<input value={firstname} onChange={ e => setFirstname(e.target.value)}/>
<input value={lastname} onChange={ e => setLastname(e.target.value)}/>
<SubmitButton/>
</div>
);
};
This might help. (credits - https://goshakkk.name/form-recipe-disable-submit-button-react/)
import React from "react";
import ReactDOM from "react-dom";
class SignUpForm extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
};
}
handleEmailChange = evt => {
this.setState({ email: evt.target.value });
};
handlePasswordChange = evt => {
this.setState({ password: evt.target.value });
};
handleSubmit = () => {
const { email, password } = this.state;
alert(`Signed up with email: ${email} password: ${password}`);
};
render() {
const { email, password } = this.state;
const isEnabled = email.length > 0 && password.length > 0;
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Enter email"
value={this.state.email}
onChange={this.handleEmailChange}
/>
<input
type="password"
placeholder="Enter password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
<button disabled={!isEnabled}>Sign up</button>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<SignUpForm />, rootElement);
export default function SignUpForm() {
const [firstName, onChangeFirstName] = useState("");
const [lastName, onChangeLastName] = useState("");
const [phoneNumber, onChangePhoneNumber] = useState("");
const areAllFieldsFilled = (firstName != "") && (lastName != "") && (phoneNumber != "")
return (
<Button
title="SUBMIT"
disabled={!areAllFieldsFilled}
onPress={() => {
signIn()
}
}
/>
)
}
Similar approach as Shafie Mukhre's!