Forms and inputs can assume different states(one or more inputs are invalid vs All inputs are valid).
When to validate form
About getting input value, There is two ways.(useState or useRef);
1. useState
const [enteredName, setEnteredName] = useState("");
<input
type="text"
id="name"
onChange={nameInputHandler}
ref={inputRef}
value={enteredName}
/>
Adding condition in your form submit handler.
if (enteredName.trim() === "") {
return;
}
we also need some error messages and style of invalidity for better UX
To achieve this, we can use 'useState' with boolean state and send error message or style to user as of state.
const [validName, setValidName] = useState(true);
.
.
const nameInputClasses = validName ? "form-control" : "form-control invalid";
.
.
<div className={nameInputClasses}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
onChange={nameInputHandler}
ref={inputRef}
value={enteredName}
/>
{!validName && <p className="error-text">Name must not be empty</p>}
</div>
For more specific error condition, we can add more state and useEffect().
It is to send http request to server in real world app that we use useEffect here.
we can add a state about whether this input are touched or not so that we can send some error style to user.
const [touchedName, setTouchedName] = useState(false);
.
.
const submitHandler = (evt) => {
evt.preventDefault();
setTouchedName(true);
.
.
}; // if form is submitted, we guess that user already touched input.
const nameInputIsInvalid = !validName && touchedName; //more specific condition
return (
<form onSubmit={submitHandler}>
<div className={nameInputClasses}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
onChange={nameInputHandler}
ref={inputRef}
value={enteredName}
/>
{nameInputIsInvalid && (
<p className="error-text">Name must not be empty</p>
)}
</div>
<div className="form-actions">
<button>Submit</button>
</div>
</form>
);
Regarding to losing focus action, we have built-in event 'onBlur'.
Taking same logic from submit handler, create new handler and give it to 'onBlur' event.
const nameInputBlurHandler = (evt) => {
setTouchedName(true);
if (enteredName.trim() === "") {
setValidName(false);
return;
}
};
return (
.
.
<input
type="text"
id="name"
onChange={nameInputHandler}
ref={inputRef}
value={enteredName}
onBlur={nameInputBlurHandler}
/>
.
.
)
By the way, we have duplicated logic in here, so we need to refactor code surely.
To detect any changes or invalid input on every key stroke, we need to use similar logic in input changing handler.
const nameInputHandler = (evt) => {
setEnteredName(evt.target.value);
if (evt.target.value.trim() !== "") {
setValidName(true);
}
};
The reason that we use event.target.value in if condition is react state changing is scheduled process(asynchronous). So if we just use 'enteredName' state in this condition, we will just get old previous state because the function process is not end yet.
Then Let's take a look at refactoring part.
Since we have changed valid state as to our input state, we don't need valid state actually.
Whenver input state is changed, the whole component function will be re-rendered. Even though we don't have any valid state, if we just set some condition varibale from this state, it will be also re-rendered altogether.
Here is refactored code.
import { useState } from "react";
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState("");
const [touchedName, setTouchedName] = useState(false);
const validName = enteredName.trim() !== "";
const nameInputIsInvalid = !validName && touchedName;
const nameInputHandler = (evt) => {
setEnteredName(evt.target.value);
};
const nameInputBlurHandler = (evt) => {
setTouchedName(true);
};
const submitHandler = (evt) => {
evt.preventDefault();
setTouchedName(true);
if (!validName) {
return;
}
console.log(enteredName);
setEnteredName("");
setTouchedName(false);
};
const nameInputClasses = nameInputIsInvalid
? "form-control invalid"
: "form-control";
return (
<form onSubmit={submitHandler}>
<div className={nameInputClasses}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
onChange={nameInputHandler}
value={enteredName}
onBlur={nameInputBlurHandler}
/>
{nameInputIsInvalid && (
<p className="error-text">Name must not be empty</p>
)}
</div>
<div className="form-actions">
<button>Submit</button>
</div>
</form>
);
};
export default SimpleInput;
In real world app, there is a few more inputs in a form.
Regarding to many inputs, we should change overall form validity.
let formIsValid = false;
if (validName) {
formIsValid = true;
}
With pre-defined valid input booleans, we can set overall validity boolean.(we just have one input here though.)
There is also a third-party library which handle form content 'Formik'.