Unexpected behavior with my native react input validation - reactjs

I created an input field which am trying to validate
const [name, setName] = useState('');
const [formErrors, setFormErrors] = useState({});
<p>Name</p>
<input
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Error> {formErrors.name}</Error>
<Button
onClick={handleSubmit}
>
Submit
</Button>
OnClick of the submit button it checks if the name field is empty in the handleSubmit function
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'Name is required!';
}
return errors;
};
const handleSubmit = async (e) => {
const val = {name};
setFormErrors(validate(val));
if (Object.keys(formErrors).length === 0) {
console.log('No empty');
}else{
console.log('Empty');
}
};
The issue am having is that it lags behind in response. For example if the name field is empty it console's Not empty, on first click of the buttton, if I then click the button again it then console's the correct data which is 'Empty'.

This is becasue the state is not set until the component is being re-render therefore the formErrors state is {} until the handle function ends. Create a new constant to hold the errors and use these to console the outcome instead of the state itself if you still need to do something during the event, however use the state inside the JSX to render correctly since state will have been changed by then.
const handleSubmit = async (e)=>{
const val = {name};
const errors = validate(val)
setFormErrors(errors);
if (Object.keys(errors).length === 0) {
console.log('No empty');
}else{
console.log('Empty');
}
}

Related

React.Js Input Field looses the focus on every Keystroke

When I press a key for inputting the value, the text field loses the focus and I again need to click on the input field.
import React, { useState } from "react";
import styled from "styled-components";
import Button from "../../UI/Button/Button";
import "./CourseInput.css";
const CourseInput = (props) => {
const [enteredValue, setEnteredValue] = useState("");
const [isValid, setIsValid] = useState(true);
Here is the code to handle input field changes
const goalInputChangeHandler = (event) => {
if (event.target.value.trim().length > 0) {
setIsValid(true);
}
setEnteredValue(event.target.value);
};
Here is the code to handle the form submission.
const formSubmitHandler = (event) => {
event.preventDefault();
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
setEnteredValue("");
};
const FormControl = styled.form`
// ....some CSS
`;
return (
<form onSubmit={formSubmitHandler}>
<FormControl className={!isValid && " invalid"}>
<label>Course Goal</label>
<input
value={enteredValue}
type="text"
onChange={goalInputChangeHandler}
/>
</FormControl>
<Button type="submit">Add Goal</Button>
</form>
);
};
export default CourseInput;
I've tried to recreate your issue locally and wasn't able to.
However, I believe the input loses focus as you are setting isValid to true, triggering a re-render and therefor input focus gets lost.
Instead of setting isValid in goalInputChangeHandler, leverage useEffect hook, which will allow you to perform side effects (e.g. when changing the value of the input, a side effect is to update the isValid value) like below:
useEffect(() => {
if (enteredValue.length > 0) {
setIsValid(true);
} else {
setIsValid(false);
}
}, [enteredValue]);
const goalInputChangeHandler = (event) => {
setEnteredValue(event.target.value);
};
This also will allow you to update your submit function to be as follows:
const formSubmitHandler = (event) => {
event.preventDefault();
props.onAddGoal(enteredValue);
};
As the useEffect will ensure isValid is correctly updated.

How to configure 'value' in the input field in React?

In React, I have a textbox with a Submit button that is visible when the user clicks 'Wave At Me' and is not visible after the user clicks 'Submit'. What do I put for value in the input tag? How do I get value should I can use it in the setFormText method? In the component class, value is below, but what is the equivalent for the function code?
<input type="text" value={this.state.value} onChange={this.handleChange} />
My code is below in the default App() function, currently the user is unable to change the text:
const [currentVisibility, setVisibility] = useState(false);
const [currentFormText, setFormText] = useState("");
//wave at me button just shows the form
const wave = async () => {
setVisibility(true);
console.log("form appears")
}
// I don't think it's correct to use changeValue function
const changeValue = async () => {
console.log("formed had some text");
}
const handleChange = async () => {
console.log("form was changed");
}
//the handle submit should read the value from the input field to use in the parameter string of the wave() function.
const handleSubmit = async () => {
try {
setVisibility(false);
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
let count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
//change to currentText instead of water bottle
const waveTxn = await wavePortalContract.wave("water bottle");
console.log("Mining...", waveTxn.hash);
await waveTxn.wait();
console.log("Mined -- ", waveTxn.hash);
count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
} else {
console.log("Ethereum object does not exist!");
}
} catch (error) {
console.log(error)
}
}
return {currentVisibility && (
<form onSubmit={handleSubmit}>
<label>
Message:
<input type="text" value="" onChange={handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
)}
The useState hook returns the state value and a setter function. So in your case, currentFormText is the state value and setFormText is the setter function. Thus, your input should read:
<input type="text" value={currentFormText} onChange={handleChange} />
If you do this, you'll notice you can't change the input's value. That's because React is now "controlling" the value of the input; in other words, React is now the "source of truth" for this input (rather than the browser/HTML itself). Because of this, you'll need to add to your handler function:
// we certainly don't need the `async` keyword here!
const handleChange = (event) => {
console.log("form was changed");
setFormText(event.target.value);
}
On form submit refresh the page, you need to put this code e.preventDefault(); in handleSubmitfunction !
When handleChange function call you need to set the input value in currentFormText state that's why you unable to change the text !
Try this code it's work for me
function App() {
const [currentVisibility, setVisibility] = useState(true);
const [currentFormText, setFormText] = useState("");
//wave at me button just shows the form
const wave = async () => {
setVisibility(true);
console.log("form appears")
}
const changeValue = async () => {
console.log("formed had some text");
}
const handleChange = async (value) => {
setFormText(value)
console.log("form was changed");
}
//the handle submit should read the value from the input field to use in the parameter string of the wave() function.
const handleSubmit = async (e) => {
try {
e.preventDefault();
setVisibility(false);
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
let count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
//change to currentText instead of water bottle
const waveTxn = await wavePortalContract.wave("water bottle");
console.log("Mining...", waveTxn.hash);
await waveTxn.wait();
console.log("Mined -- ", waveTxn.hash);
count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
} else {
console.log("Ethereum object does not exist!");
}
} catch (error) {
console.log(error)
}
}
return (
currentVisibility &&
<form onSubmit={(e) => handleSubmit(e)}>
<label>
Message:
<input type="text" value={currentFormText} onChange={(e) => handleChange(e.target.value)} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
export default App;

React form useState object doesn't work as I want

I have a problem with a React app. I have a form with two inputs, and when I submit the form with empty inputs, it should render an error message in each of them. The problem is that it doesn't show for the first input. How can I fix it to display an error in each of those? The implementation is in useForm.js.
FormUI image:
My code:
Form.js
const Form = () => {
const formLogin = () => {
console.log("Callback function when form is submitted!");
console.log("Form Values ", values);
}
const {handleChange, values, errors, handleSubmit} = useForm(formLogin);
return (
<Wrapper>
<form onSubmit={handleSubmit}>
<div className="govgr-form-group gap-bottom">
<label className="govgr-label govgr-!-font-weight-bold" htmlFor="code">Code*</label>
{errors.code && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>{errors.code}</p>}
<input className={`govgr-input govgr-!-width-three-quarter ${errors.code ? 'govgr-error-input' : ''}`} id="code" name="code" type="text" onChange={handleChange} />
</div>
<fieldset>
<div className="govgr-form-group">
<label className="govgr-label govgr-!-font-weight-bold" htmlFor="first">Name*</label>
{errors.first && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>{errors.first}</p>}
<input className={`govgr-input govgr-!-width-three-quarter ${errors.first ? 'govgr-error-input' : ''}`} id="first" name="first" type="text" onChange={handleChange} />
</div>
</fieldset>
<button type="submit" className="govgr-btn govgr-btn-primary btn-center">Save</button>
</form>
</Wrapper>
);
};
export default Form;
useForm.js:
const useForm = (callback) => {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const validate = (event, name, value) => {
event.persist();
switch (name) {
case "code":
if (value.trim() === "" || value.trim() === null) {
setErrors({
...errors,
code: "Code is required",
});
} else {
let newObj = omit(errors, "code");
setErrors(newObj);
}
break;
case "first":
if (value.trim() === "" || value.trim() === null) {
setErrors({
...errors,
first: "Name is required",
});
} else {
let newObj = omit(errors, "first");
setErrors(newObj);
}
break;
default:
break;
}
};
const handleChange = (event) => {
event.persist();
let name = event.target.name;
let val = event.target.value;
validate(event, name, val);
setValues({
...values,
[name]: val,
});
};
const handleSubmit = (event) => {
if (event) event.preventDefault();
if (
Object.keys(errors).length === 0 &&
Object.keys(values).length !== 0 &&
values.code &&
values.first
) {
callback();
} else {
if (!values.code) {
setErrors({
...errors,
code: "Code is required.",
});
}
if (!values.first) {
setErrors({
...errors,
first: "Name is required.",
});
}
}
};
return {
values,
errors,
handleChange,
handleSubmit
};
};
export default useForm;
You can simplify your code by having only a single validation routine. You can fix the error you mention, having only a single error at a time, by using the current state as passed into setState by the framework. The construct for this is
setState(e => /* whatever you want to return relative to e */);
Ultimately, the error you're seeing is because the component, and its code, is rerendered after every change of state (but they are batched for performance reasons), so your code is only seeing an older version of the state when you access it directly. If you want the actual current state when changing it, it's always best to have the framework pass it directly to the change-state function. If you always follow this pattern, you will rarely have any problems with the react state model.
The hard problem however isn't any of this. The hard problem is the way you're removing errors. First, from a UI perspective, it's a little inconsistent to only show a field as an error when it is edited, because the form is erroneous when it's first shown. But, ignoring that, the underlying technical problem is removing a property from an object. Normally we would use arrays rather than objects when we need to do this, or we would use underscore's omit function. I see in your code you call a function called omit, but you don't have an explanation as to what that is. In your case though, this can be easily solved by never removing the property. Instead, have each as an object with a valid flag. This would allow you to remove the switch statement completely.
You could go further and roll the value in there too, making a complete single state for each field, but I won't demonstrate that here.
I haven't tested this, so there may be a typo or two lurking in here.
const useForm = (callback) => {
const [values, setValues] = useState({code: '', first: ''});
const [errors, setErrors] = useState({first: {message: "Name is required", valid: true}, code: {message: "Code is required", valid: true}});
const isEmpty = val => val.trim() === "" || val.trim() === null);
const validate = (name, val) => {
setErrors(e => ({...e, [name]: {...e[name], valid: isEmpty(val)}}))
return errors[name].valid;
};
const handleChange = (event) => {
let name = event.target.name;
let val = event.target.value;
setValues({...values, [name]: val});
validate(name, val);
};
const handleSubmit = event => {
if (event) event.preventDefault();
const valid = validate('code', values.code) && validate('first', values.first);
if ( valid ) {
callback();
}
};
return {
values,
errors,
handleChange,
handleSubmit
};
};
export default useForm;
And, you'd have to change your HTML template slightly:
{!errors.first.valid && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>{errors.first.message}</p>}
I realise that this new validate function is a huge departure from what you had, but based on your own code I think you can handle it. Basically, it's just using destructuring and variable property accessors. This looks like its well within your capabilities to understand, but feel free to ask more questions if any of it is confusing or doesn't actually work :)

How to display data entered in input field in react js?

I want to show the username in the alert box. But so far it's not working. Here is my code:
function App() {
const [nameInForm, setNameInForm] = useState("");
const [username, setUserName] = useState("");
const inputName = (event) => {
setNameInForm(event.target.value);
}
const handleSubmit = () => {
setUserName(nameInForm);
alert("Welcome " + username);
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="text" value={nameInForm} onChange={inputName} />
<button type="submit"> Login </button>
</form>
</>
);
}
If I don't use the form tag and use onClick={handleSubmit} on the button still the code does not work.
Setting state is kinda async operation. You can not use the new state value on the next line of code in console.log() or alerts etc...
In your example, you already have the username in "nameInForm" state, you can use it
const handleSubmit = () => {
setUserName(nameInForm);
alert("Welcome " + nameInForm);
}
either "username" and useEffect hook can be used which is nice way to watch state changes:
//the function is updating state:
const handleSubmit = () => {
setUserName(nameInForm);
}
// the useEffect hook shows new state after it has been updated
useEffect(() => {
alert("Welcome " + username);
}, [username])
You should not call setUserName in handleSubmit. As setUserName is asynchronous it will be resolved on next render so you should do
const handleSubmit = () => {
alert("Welcome " + nameInForm);
}

want to show validation error on handlechange using react hooks

I am new to React 16, Please find the below code which I am using for Validation using hooks. I have used a reusable Select box and on the value of the option, I am showing an input box.
Validation rules: if the select box is empty on click of submit i want to show error and hide it when I select a value then if the input box is empty want to show error and hide when we give valid input.
Problem : When error message appears for select box and after selecting the value when input box appears it shows the error for that too so it's like the first look of input box is with the validation error. I want to show it when user clicks submit button and onchange after valid entry it should go away.
Really appreciate the help!
const Step3 = (props) => {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSent, setIsSent] = useState(false);
const { handleChange, handleSubmit } = useForm(validate);
useEffect(() => { if (Object.keys(errors).length === 0 && isSubmitting) { } }, [errors]);
const onChange = (event) => {
event.persist && event.persist();
setIsSubmitting(false);
const newValues = {...values,[event.target.name]:event.target.value};
isSent && setErrors(validate(newValues));
setValues(values => newValues);
};
const handleSubmit = (event) => {
if (event) event.preventDefault();
setErrors(validate(values));
setIsSubmitting(true);
setIsSent(true);
};
return (
<div>
<form onSubmit={handleSubmit} noValidate>
<Select
name="abc"
label="ABC"
options={options}
onChange={onChange}
/>
{errors.abc && (<p className="help is-danger">{errors.abc}</p>)}
{values.abc=='Apple' && <input name="xyz" value={values.xyz||'' onChange={onChange}}/>
{errors.xyz && (<p className="help is-danger">{errors.xyz}</p> )}
}
<Button type={submit}>
Submit
</Button>
</form>
</div>
);
};
function validate(values) {
let errors = {}
if (!values.abc)
{ errors.abc= ' required'; }
if (!values.xyz) {
errors.xyz= 'required';
}
return errors;
};
useForm({ mode: 'onChange' })
Doc:
https://react-hook-form.com/api#useForm
Example:
https://codesandbox.io/s/react-hook-form-defaultvalues-v6-forked-s9buh?file=/src/index.js

Resources