import { useState } from 'react';
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
// check if the name input was focused or not
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
// check if name input is valid
const enteredNameIsValid = enteredName.trim() !== '';
// don't need to manage it as state
// because this code will also be executed
// when other state(enteredName, enteredNameTouched) changes
// check if the name input is invalid and the name input was focused
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
// check if the entire form is valid
let formIsValid = false;
if (enteredNameIsValid) {
formIsValid = true;
}
// onChange : nameInputChangeHandler
// setEnteredName
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
// onBlur : nameInputBlurHandler
// setEnteredNameTouched
const nameInputBlurHandler = (event) => {
setEnteredNameTouched(true);
};
const formSubmissionHandler = (event) => {
// when submitting with button,
// it sends the HTTP request to the server in default
// so we should use event.preventDefault() to prvent it
event.preventDefault();
setEnteredNameTouched(true);
// if the form is invalud, it doesn't execute the code below
if (!enteredNameIsValid) {
return;
}
// nameInputRef.current.value = ''; => NOT IDEAL, DON'T MANIPULATE THE DOM
setEnteredName('');
setEnteredNameTouched(false);
};
const nameInputClasses = nameInputIsInvalid
? 'form-control invalid'
: 'form-control';
return (
<form onSubmit={formSubmissionHandler}>
<div className={nameInputClasses}>
<label htmlFor='name'>Your Name</label>
<input
type='text'
id='name'
onChange={nameInputChangeHandler}
onBlur={nameInputBlurHandler}
value={enteredName}
/>
{nameInputIsInvalid && (
<p className='error-text'>Name must not be empty.</p>
)}
</div>
<div className='form-actions'>
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default SimpleInput;
Simplifications
If there are multiple inputs,
the logic for managing those inputs would be the same
=> why don't we manage those logic with custom-hooks?
hooks/use-input.js
import { useReducer } from 'react';
// use useReducer because the state(input value, focus state) are related
const initialInputState = {
value: '',
isTouched: false,
};
const inputStateReducer = (state, action) => {
if (action.type === 'INPUT') {
return { value: action.value, isTouched: state.isTouched };
}
if (action.type === 'BLUR') {
return { isTouched: true, value: state.value };
}
if (action.type === 'RESET') {
return { isTouched: false, value: '' };
}
return inputStateReducer;
};
const useInput = (validateValue) => {
const [inputState, dispatch] = useReducer(
inputStateReducer,
initialInputState
);
const valueIsValid = validateValue(inputState.value);
const hasError = !valueIsValid && inputState.isTouched;
const valueChangeHandler = (event) => {
dispatch({ type: 'INPUT', value: event.target.value });
};
const inputBlurHandler = (event) => {
dispatch({ type: 'BLUR' });
};
const reset = () => {
dispatch({ type: 'RESET' });
};
return {
value: inputState.value,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
reset,
};
};
export default useInput;
BasicForm.js
import useInput from '../hooks/use-input';
const isNotEmpty = (value) => value.trim() !== '';
const isEmail = (value) => value.includes('@');
const BasicForm = (props) => {
const {
value: firstNameValue,
isValid: firstNameIsValid,
hasError: firstNameHasError,
valueChangeHandler: firstNameChangeHandler,
inputBlurHandler: firstNameBlurHandler,
reset: resetFirstName,
} = useInput(isNotEmpty);
const {
value: lastNameValue,
isValid: lastNameIsValid,
hasError: lastNameHasError,
valueChangeHandler: lastNameChangeHandler,
inputBlurHandler: lastNameBlurHandler,
reset: resetLastName,
} = useInput(isNotEmpty);
const {
value: emailValue,
isValid: emailIsValid,
hasError: emailHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: resetEmail,
} = useInput(isEmail);
let formIsValid = false;
if (firstNameIsValid && lastNameIsValid && emailIsValid) {
formIsValid = true;
}
const submitHandler = event => {
event.preventDefault();
if (!formIsValid) {
return;
}
console.log('Submitted!');
console.log(firstNameValue, lastNameValue, emailValue);
resetFirstName();
resetLastName();
resetEmail();
};
const firstNameClasses = firstNameHasError ? 'form-control invalid' : 'form-control';
const lastNameClasses = lastNameHasError ? 'form-control invalid' : 'form-control';
const emailClasses = emailHasError ? 'form-control invalid' : 'form-control';
return (
<form onSubmit={submitHandler}>
<div className='control-group'>
<div className={firstNameClasses}>
<label htmlFor='name'>First Name</label>
<input
type='text'
id='name'
value={firstNameValue}
onChange={firstNameChangeHandler}
onBlur={firstNameBlurHandler}
/>
{firstNameHasError && <p className="error-text">Please enter a first name.</p>}
</div>
<div className={lastNameClasses}>
<label htmlFor='name'>Last Name</label>
<input
type='text'
id='name'
value={lastNameValue}
onChange={lastNameChangeHandler}
onBlur={lastNameBlurHandler}
/>
{lastNameHasError && <p className="error-text">Please enter a last name.</p>}
</div>
</div>
<div className={emailClasses}>
<label htmlFor='name'>E-Mail Address</label>
<input
type='text'
id='name'
value={emailValue}
onChange={emailChangeHandler}
onBlur={emailBlurHandler}
/>
{emailHasError && <p className="error-text">Please enter a valid email address.</p>}
</div>
<div className='form-actions'>
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default BasicForm;