How to disable form submit button until all input fields are filled?! ReactJS ES2015 - javascript

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!

Related

Having problem with controlled form validation in Reactjs

Hi i'm newbie learning to code and start with Js, Reactjs. I convert my code from class component to functional component and then my validation stop working. i have looking around but yet to find answer, can someone help? ^^"
export default function AddComment() {
const [formValue, setFormValue] = useState ({
rating: '5',
yourname: '',
comment: '',
})
const {rating, yourname, comment} = formValue
const [isModalOpen, setIsModalOpen] = useState(false)
const [touched, setTouched] = useState({yourname: false})
const handleInputChange = (event) => {
const {name, value} = event.target
setFormValue((prevState) => {
return {
prevState,
[name]:value,
};})
}
const handleBlur = (field) => (evt) => {
setTouched({
touch: {...touched, [field]: true }
})
};
function validate(yourname) {
const errors = {
yourname: ''
};
if (touched.yourname && yourname.length <2)
errors.yourname = 'Your name should be atleast 2 characters';
else if (touched.yourname && yourname.length >= 10)
errors.yourname = 'Your name should be less than 10 characters';
return errors
}
const errors = validate(yourname)
return (
<ModalBody>
<Form onSubmit={handleSubmit}>
<FormGroup row>
<Label htmlFor='yourname' md = {12}>Your Name</Label>
<Col md = {12}>
<Input type= 'text' id= 'yourname' name= 'yourname' value= {yourname}
valid={errors.yourname === ''} invalid= {errors.yourname !== ''}
onBlur= {handleBlur('yourname')} onChange= {handleInputChange}/>
<FormFeedback>{errors.yourname}</FormFeedback>
</Col>
</FormGroup>
</Form>
</ModalBody>
)
}
Not sure this will solve your problem, but notice on handleInputChange you are not spreading the previous state, so the object you're setting state with looks like:
{
prevState: {...the actual previous state},
[name]: ...what you wanted to change
}

React render [object Object] when using useReducer instead of JSX

So I was learning my React course today on Udemy and came across an error where the react app renders [object Object] instead of the JSX (which is supposed to be an empty input box), also, since the input box has onChange method, The input value is also unable to change. Here is the image of the application and also the code given below:-
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
const emailReducer = (state, action) => {
if(action.type === 'USER_INPUT'){
return {value: action.value, isValid: action.value.includes('#')};
}
if(action.type === 'INPUT_BLUR'){
return {value: state.value, isValid: state.value.includes('#')};
}
return { value: '', isValid: false };
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState("");
// const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: '',
isValid: false
});
useEffect(() => {
console.log("EFFECT RUNNING");
return () => {
console.log("EFFECT CLEANUP");
};
}, []);
const emailChangeHandler = (event) => {
dispatchEmail({type: 'USER_INPUT', value: event.target.value});
setFormIsValid(
emailState.isValid && enteredPassword.trim().length > 6
);
};
const passwordChangeHandler = (event) => {
setEnteredPassword(event.target.value);
setFormIsValid(
emailState.isValid && event.target.value.trim().length > 6
);
};
const validateEmailHandler = () => {
dispatchEmail({type: 'INPUT_BLUR'});
};
const validatePasswordHandler = () => {
setPasswordIsValid(enteredPassword.trim().length > 6);
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, enteredPassword);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
id="email"
value={emailState}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordIsValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={enteredPassword}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
Your emailState is an object with the following shape:
{ value: string, isValid: boolean }
So in your input field you need to use the value attribute:
<input
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
You are passing the entire emailState object to the input when you likely want the nested value property. You can pass emailState.value to the input's value prop, or destructure the state beforehand and pass value directly.
const [{ isValid, value }, dispatchEmail] = useReducer(emailReducer, {
value: '',
isValid: false
});
...
<input
id="email"
value={value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>

Password States are reacting one character late

Im checking to see if the register forms passwords match, and when they do, something changes. but its happening on 1 "onChange" too late. Ex. User enters "DOG" as the password. when the retype it in the 2nd input, "DOG" doesnt work. but it does if they enter another character or delete one character (Ex. "DOGX" or deleting "G" so its "DO")
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import "./register.css";
function RegisterBoard() {
const history = useHistory();
const [register, changeRegister] = useState({
password: false,
repeatPassword: false,
});
const [info, changeInfo] = useState({
password: "",
repeatPassword: "",
});
const changeValue = (e) => {
const { name, value } = e.target;
changeInfo((prev) => {
return {
...prev,
[name]: value,
};
});
};
const input = (e) => {
const target = e.target.dataset.name;
if (target != "repeatPassword") {
changeRegister({
...register,
[target]: true,
});
} else {
if (info.password != info.repeatPassword) {
changeRegister({
...register,
repeatPassword: false,
});
} else {
changeRegister({
...register,
repeatPassword: true,
});
}
}
};
return (
<div className="registration-form">
<form>
<div>
<input
name="password"
data-name="password"
onChange={(e) => {
changeValue(e);
input(e);
}}
className="password"
type="password"
placeholder="ENTER YOUR PASSWORD HERE"
/>
<div className="animated-button">
</div>
</div>
<div>
<input
id="pwd"
name="repeatPassword"
data-name="repeatPassword"
onChange={(e) => {
changeValue(e);
input(e);
}}
className="repeat-password"
type="password"
placeholder="REPEAT YOUR PASSWORD HERE"
/>
</div>
</div>
</form>
</div>
);
}
export default RegisterBoard;
I guess this is because you are calling both 'changeValue' and 'input' functions within the inputs onChange attribute. Since they are firing at the same time, 'input' is not using the most recent value for 'info', because 'changeValue' hasn't set the new state yet.
Either call the input function within a useEffect hook which is dependent on changes to 'info's' state, or use e.target.value instead of info's state within the 'input' function to compare info.password != info.repeatPassword
EDIT: here is the useEffect way, it simplifies it and you can remove your input function completely: https://codesandbox.io/s/jolly-khorana-8s63b?file=/src/App.js
import React, { useState, useEffect } from "react";
import "./styles.css";
function RegisterBoard() {
const [register, changeRegister] = useState({
password: false,
repeatPassword: false
});
const [info, changeInfo] = useState({
password: "",
repeatPassword: ""
});
const changeValue = (e) => {
const { name, value } = e.target;
changeInfo((prev) => {
return {
...prev,
[name]: value
};
});
};
useEffect(() => {
let password = false;
let repeatPassword = false;
if (info.password !== "") {
password = true;
if (info.password === info.repeatPassword) {
repeatPassword = true;
}
}
changeRegister({ password, repeatPassword });
}, [info]);
return (
<div className="registration-form">
<form>
<div>
<input
name="password"
data-name="password"
onChange={changeValue}
className="password"
type="password"
placeholder="ENTER YOUR PASSWORD HERE"
/>
<div className="animated-button"></div>
</div>
<div>
<input
id="pwd"
name="repeatPassword"
data-name="repeatPassword"
onChange={changeValue}
className="repeat-password"
type="password"
placeholder="REPEAT YOUR PASSWORD HERE"
/>
</div>
</form>
<div>{info.password}</div>
<div>{info.repeatPassword}</div>
<div>{register.repeatPassword ? "match" : "don't match"}</div>
</div>
);
}
export default function App() {
return (
<div className="App">
<RegisterBoard />
</div>
);
}
You're definitely going to want to implement a useEffect here to update the UI every time the password & repeatPassword state changes, to ensure that after the last character is typed that you get the full password. Inside the useEffect is where you'll write your conditional logic. What I provided is just a good example...
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import "./register.css";
function RegisterBoard() {
const history = useHistory();
const [password, setPassword] = useState('')
const [repeatPassword, setRepeatPassword] = useState('')
//const [register, changeRegister] = useState(false);
const changeValue = (e) => {
const { name, value } = e.target.value;
const input = (e) => {
const target = e.target.dataset.name;
if (target != "repeatPassword") {
changeRegister({
...register,
[target]: true,
});
} else {
if (info.password != info.repeatPassword) {
changeRegister({
...register,
repeatPassword: false,
});
} else {
changeRegister({
...register,
repeatPassword: true,
});
}
}
};
useEffect(() => {
if((password !== "" && repeatPassword !== "") && (password !==
repeatPassword)){
console.log("PASSWORDS DO NOT MATCH!!!")
}
console.log(password, repeatPassword)
}, [password, repeatPassword])
return (
<div className="registration-form">
<form>
<div>
<input
name="password"
data-name="password"
onChange={(e) => changeValue(e)}
className="password"
type="password"
placeholder="ENTER YOUR PASSWORD HERE"
/>
<div className="animated-button">
</div>
</div>
<div>
<input
id="pwd"
name="repeatPassword"
data-name="repeatPassword"
onChange={(e) => changeValue(e)}
className="repeat-password"
type="password"
placeholder="REPEAT YOUR PASSWORD HERE"
/>
</div>
</div>
</form>
</div>
);
}
export default RegisterBoard;

How to create two reusable invoking custom hook?

I created some logic that is reusable code anywhere. I have an input.jsx component for handling input elements, and use-form.js custom hook for managing all of the stuff and I have a basic-form component for creating form logic. Inside of basic-form component, I'm calling the custom hook many times. That is tedious and quite repetitive* so I wanna create two reusable invoking custom hooks in the input.jsx component
input.component.jsx
const Input = (props) => {
return (
<div className={`form-control ${props.error && 'invalid'}`}>
<label htmlFor={props.id}>{props.label}</label>
<input
type={props.type}
id={props.id}
value={props.value}
onChange={props.changeHandler}
onBlur={props.blurHandler}
/>
{props.error && <p className="error-text">{props.label} must be entered!</p>}
</div>
);
};
use-input.js
import { useReducer } from 'react';
const inputReducer = (state, action) => {
switch (action.type) {
case 'INPUT':
return { value: action.value, isTouched: state.isTouched };
case 'BLUR':
return { isTouched: true, value: state.value };
case 'RESET':
return { value: '', isTouched: false };
}
return state;
};
export const useInput = (validateValue) => {
const [state, dispatch] = useReducer(inputReducer, {
value: '',
isTouched: false,
});
const valueIsValid = validateValue(state.value);
const error = !valueIsValid && state.isTouched;
const valueChangeHandler = (e) => {
dispatch({ type: 'INPUT', value: e.target.value });
};
const inputBlurHandler = () => {
dispatch({ type: 'BLUR' });
};
const reset = () => {
dispatch({ type: 'RESET' });
};
return {
value: state.value,
isValid: valueIsValid,
error,
valueChangeHandler,
inputBlurHandler,
reset,
};
};
basic-form.component.jsx
import { useInput } from '../hooks/use-input';
import Input from './input.component';
const BasicForm = () => {
const {
value: name,
isValid: nameIsValid,
error: nameHasError,
valueChangeHandler: nameChangeHandler,
inputBlurHandler: nameBlurHandler,
reset: nameReset,
} = useInput((value) => value.trim() !== '');
const {
value: surname,
isValid: surnameIsValid,
error: surnameHasError,
valueChangeHandler: surnameChangeHandler,
inputBlurHandler: surnameBlurHandler,
reset: surnameReset,
} = useInput((value) => value.trim() !== '');
const {
value: email,
isValid: emailIsValid,
error: emailHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: emailReset,
} = useInput((value) => /^\S+#\S+\.\S+$/.test(value));
let formIsValid = false;
if (nameIsValid && surnameIsValid && emailIsValid) {
formIsValid = true;
}
const formSubmitHandler = (e) => {
e.preventDefault();
nameReset();
surnameReset();
emailReset();
};
return (
<form onSubmit={formSubmitHandler}>
<div className="control-group">
<Input
id="name"
label="First Name"
type="text"
error={nameHasError}
value={name}
changeHandler={nameChangeHandler}
blurHandler={nameBlurHandler}
/>
<Input
id="surname"
label="Last Name"
type="text"
error={surnameHasError}
value={surname}
changeHandler={surnameChangeHandler}
blurHandler={surnameBlurHandler}
/>
<Input
id="email"
label="Email"
type="email"
error={emailHasError}
value={email}
changeHandler={emailChangeHandler}
blurHandler={emailBlurHandler}
/>
</div>
<div className="form-actions">
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default BasicForm;
The problem with that is we must return two values from input.jsx to basic-form.jsx. The first value shows the property isValid or not. Second, a function for reseting every individual input element. Also, we must receive a function that validates the logic from the parent to the children components. How can we solve this problem? I think we should use useRef, useImperativeHandle and forwardRef but how?
Please vote up for more people to see.

Switching input field form validation in react

Currently, in my form, the input fields get validated as soon as the user types in something. Here's the code for that-
index.js
import React from "react";
import ReactDOM from "react-dom";
import ShowError from "./ShowError";
import "./styles.css";
class App extends React.Component {
state = {
email: "",
name: "",
mobile: "",
errors: {
email: "",
name: "",
mobile: ""
},
nameError: false,
emailError: false,
mobileError: false,
formError: false
};
showMsg = () => {
if (!this.state.formError) {
alert("Error");
}
};
validateFunc = (name, value) => {
let error = this.state.errors;
let nameError = this.state.nameError;
let emailError = this.state.emailError;
let mobileError = this.state.mobileError;
switch (name) {
case "name":
nameError = !/^[a-zA-Z ]+$/.test(value);
error.name = nameError ? " is Invalid" : "";
break;
case "email":
emailError = !/^([\w.%+-]+)#([\w-]+\.)+([\w]{2,})$/i.test(value);
error.email = emailError ? " is Invalid" : "";
break;
case "mobile":
mobileError = !/^[0-9]{10}$/.test(value);
error.mobile = mobileError ? " is Invalid" : "";
break;
}
this.setState({
errors: error,
nameError: nameError,
emailError: emailError,
mobileError: mobileError
});
};
handleInput = event => {
const name = event.target.name;
const value = event.target.value;
this.setState(
{
[name]: value
},
this.validateFunc(name, value)
);
};
handleSubmit = event => {
event.preventDefault();
let formError =
this.state.nameError || this.state.emailError || this.state.mobileError;
this.setState({
formError: formError
});
};
render() {
return (
<div className="App">
<h1>Basic Form Validation</h1>
<form className="FormStyle">
<input
className="FieldStyle"
type="text"
name="name"
placeholder="Name"
onChange={event => this.handleInput(event)}
/>
<input
className="FieldStyle"
type="email"
name="email"
placeholder="Email"
onChange={event => this.handleInput(event)}
/>
<input
className="FieldStyle"
type="number"
name="mobile"
placeholder="Mobile"
onChange={event => this.handleInput(event)}
/>
<button
className="FieldStyle"
type="submit"
onClick={event => this.handleSubmit(event)}
>
SUBMIT
</button>
</form>
<ShowError error={this.state.errors} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
ShowError.js
import React from "react";
const ShowError = props => {
return Object.keys(props.error).map((field, index) => {
if (props.error[field].length > 0) {
return (
<p key={index}>
{field} {props.error[field]}
</p>
);
} else {
return "";
}
});
};
export default ShowError;
Expected Behaviour- What I want is, the fields should get validated as soon as the user focuses on next input field or when 'tab' key is pressed, I don't want to validate while the user is typing but when the user switches the field. How can I achieve the above behaviour? Thanks a lot!
P.S. - Would be better if I can achieve this without using libraries like redux-forms.
Use onBlur so the event is triggered when the user leaves the component
onBlur={event => this.handleInput(event)}

Categories