React Form Validation Using React Hooks
React 16.8 updates introduce a new feature in their library called Hooks. Hooks is one of the most revolutionary updates happened in React library. It gives you a lot more flexibility in writing stateful components w/out writing classes. There’s a saying “Write less, Do more” you’ll avoid much boilerplate code in your React application.
So in this article I’m going to share with you on how I created my own form validation using React hooks.
Before we get started, the complete repository can be downloaded in my GitHub profile. Feel free to modify the code if you have something to share or comments on it Enjoy!
Okay so first let us create a file called useForm.js in this file we should place our form validation hooks for separation of concerns. We follow the convention in React library that every functional component that uses hooks starts with “use” keyword followed by their purpose, to easily determine what they can do.
In this file, let’s import first all the hooks that we needed in React library.
import { useState, useEffect, useCallback } from 'react';
After that let’s create a function called useForm and has 3 parameters.
import { useState, useEffect, useCallback } from 'react';
function useForm(stateSchema, validationSchema = {}, callback) {
const [state, setState] = useState(stateSchema);
const [disable, setDisable] = useState(true);
const [isDirty, setIsDirty] = useState(false);
// Disable button in initial render.
useEffect(() => {
setDisable(true);
}, []);
// For every changed in our state this will be fired
// To be able to disable the button
useEffect(() => {
if (isDirty) {
setDisable(validateState());
}
}, [state, isDirty]);
// Used to disable submit button if there's an error in state
// or the required field in state has no value.
// Wrapped in useCallback to cached the function to avoid intensive memory leaked
// in every re-render in component
const validateState = useCallback(() => {
const hasErrorInState = Object.keys(validationSchema).some(key => {
const isInputFieldRequired = validationSchema[key].required;
const stateValue = state[key].value; // state value
const stateError = state[key].error; // state error
return (isInputFieldRequired && !stateValue) || stateError;
});
return hasErrorInState;
}, [state, validationSchema]);
// Used to handle every changes in every input
const handleOnChange = useCallback(
event => {
setIsDirty(true);
const name = event.target.name;
const value = event.target.value;
let error = '';
if (validationSchema[name].required) {
if (!value) {
error = 'This is required field.';
}
}
if (
validationSchema[name].validator !== null &&
typeof validationSchema[name].validator === 'object'
) {
if (value && !validationSchema[name].validator.regEx.test(value)) {
error = validationSchema[name].validator.error;
}
}
setState(prevState => ({
...prevState,
[name]: { value, error },
}));
},
[validationSchema]
);
const handleOnSubmit = useCallback(
event => {
event.preventDefault();
// Make sure that validateState returns false
// Before calling the submit callback function
if (!validateState()) {
callback(state);
}
},
[state]
);
return { state, disable, handleOnChange, handleOnSubmit };
}
export default useForm;
The first parameter in our useForm function is stateSchema. In this parameter, we should define our state in our form.
Example:
// Define your state schema
const stateSchema = {
fname: { value: '', error: '' },
lname: { value: '', error: '' },
tags: { value: '', error: '' },
};
Our stateSchema object consist all the fields in our form. As you can see here we have an fname and has object value containing the property of value and error. The value is the value on what we type in our input field and the error is the error in our input field if there’s any.
The second parameter is validationSchema. In this parameter we define our own rules in validating our state in our form.
Example:
// Define your validationStateSchema
// Note: validationStateSchema and stateSchema property
// should be the same in-order validation works!
const validationStateSchema = {
fname: {
required: true,
validator: {
regEx: /^[a-zA-Z]+$/,
error: 'Invalid first name format.',
},
},
lname: {
required: true,
validator: {
regEx: /^[a-zA-Z]+$/,
error: 'Invalid last name format.',
},
},
tags: {
required: true,
validator: {
regEx: /^(,?\w{3,})+$/,
error: 'Invalid tag format.',
},
},
};
As you’ve noticed here stateSchema property and validationSchema property is the same. Because we need to map the validation in stateSchema to validate fields that we define in stateSchema (eg: fname, lname and tags etc).
The third parameter is callback function that we execute if we submit the form. Usually we placed here the api for handling form submission.
function onSubmitForm(state) {
alert(JSON.stringify(state, null, 2));
}
Our useForm function returns values such as (state, disable, handleOnChange, handleOnSubmit)
return { state, disable, handleOnChange, handleOnSubmit };
state returns an object of current state in our form.
disable returns a boolean to be able to disable the submit button if there’s an error in the state
handleOnChange returns a function to set the values in our form state.
handleOnSubmit returns a function to submit the values in our form.
So we’re now done creating our useForm hooks and we can now hook it in our Component. Before that let’s create first our Form component.
import React from 'react';
import useForm from './useForm';function Form() {
// Define your state schema
const stateSchema = {
fname: { value: '', error: '' },
lname: { value: '', error: '' },
tags: { value: '', error: '' },
};// Define your validationStateSchema
// Note: validationStateSchema and stateSchema property
// should be the same in-order validation works!
const validationStateSchema = {
fname: {
required: true,
validator: {
regEx: /^[a-zA-Z]+$/,
error: 'Invalid first name format.',
},
},
lname: {
required: true,
validator: {
regEx: /^[a-zA-Z]+$/,
error: 'Invalid last name format.',
},
},
tags: {
required: true,
validator: {
regEx: /^(,?\w{3,})+$/,
error: 'Invalid tag format.',
},
},
};function onSubmitForm(state) {
alert(JSON.stringify(state, null, 2));
}const { state, handleOnChange, handleOnSubmit, disable } = useForm(
stateSchema,
validationStateSchema,
onSubmitForm
);const errorStyle = {
color: 'red',
fontSize: '13px',
};return (
<div>
<form onSubmit={handleOnSubmit}>
<div>
<label htmlFor="fname">
First name:
<input
type="text"
name="fname"
value={state.fname.value}
onChange={handleOnChange}
/>
</label>
{state.fname.error && <p style={errorStyle}>{state.fname.error}</p>}
</div><div>
<label htmlFor="lname">
Last name:
<input
type="text"
name="lname"
value={state.lname.value}
onChange={handleOnChange}
/>
</label>
{state.lname.error && <p style={errorStyle}>{state.lname.error}</p>}
</div><div>
<label htmlFor="tags">
Tags:
<input
type="text"
name="tags"
value={state.tags.value}
onChange={handleOnChange}
/>
</label>
{state.tags.error && <p style={errorStyle}>{state.tags.error}</p>}
</div><input type="submit" name="submit" disabled={disable} />
</form>
</div>
);
}export default Form;
In our Form component we used our previously created hooks called useForm and passed all the required argument to be able to use it.
Our stateSchema:
// Define your state schema
const stateSchema = {
fname: { value: '', error: '' },
lname: { value: '', error: '' },
tags: { value: '', error: '' },
};
Our validationSchema:
// Define your validationStateSchema
// Note: validationStateSchema and stateSchema property
// should be the same in-order validation works!
const validationStateSchema = {
fname: {
required: true,
validator: {
regEx: /^[a-zA-Z]+$/,
error: 'Invalid first name format.',
},
},
lname: {
required: true,
validator: {
regEx: /^[a-zA-Z]+$/,
error: 'Invalid last name format.',
},
},
tags: {
required: true,
validator: {
regEx: /^(,?\w{3,})+$/,
error: 'Invalid tag format.',
},
},
};
Our callback function in submitting form:
function onSubmitForm(state) {
alert(JSON.stringify(state, null, 2));
}
Passed all the required argument in useForm to use it.
const { state, handleOnChange, handleOnSubmit, disable } = useForm(
stateSchema,
validationStateSchema,
onSubmitForm
);
Remember our useForm hooks returns a values such as state, handleOnChange, handleOnSubmit, disable.
With that we can now use it in our Form component.
return (
<div>
<form onSubmit={handleOnSubmit}>
<div>
<label htmlFor="fname">
First name:
<input
type="text"
name="fname"
value={state.fname.value}
onChange={handleOnChange}
/>
</label>
{state.fname.error && <p style={errorStyle}>{state.fname.error}</p>}
</div><div>
<label htmlFor="lname">
Last name:
<input
type="text"
name="lname"
value={state.lname.value}
onChange={handleOnChange}
/>
</label>
{state.lname.error && <p style={errorStyle}>{state.lname.error}</p>}
</div><div>
<label htmlFor="tags">
Tags:
<input
type="text"
name="tags"
value={state.tags.value}
onChange={handleOnChange}
/>
</label>
{state.tags.error && <p style={errorStyle}>{state.tags.error}</p>}
</div><input type="submit" name="submit" disabled={disable} />
</form>
</div>
);
}
After that let us import our Form component in App.js.
import React from 'react';
import Form from './Form';import logo from './logo.svg';
import './App.css';function App() {
return (
<div className="App">
<div className="App-header">
<img className="App-logo" src={logo} alt="react-logo" />
<p>React Form Validation using React Hooks.</p>
</div>
<Form />
</div>
);
}export default App;
Here’s the final output of our created useForm hooks with Form component.
Hope it helps 😃
If you enjoy reading this article give me a clapped 👏 👏 👏
Thank you.
“Don’t be a JavaScript Mediocre.”
Follow me on twitter https://twitter.com/llaudevc/