Simplify state binding for controlled components using Hooks
Prior to the start
To read this article, you need to be aware of the following
ECMAScript 6
There is a lot of use of ES6 syntax in this article, such as deconstructing assignments and function parameter defaults, residual parameters, expansion syntax, arrow functions, etc.
Hooks
React introduced Hooks in version 16.8, which allows you to use some of the features of class components in function components.
React itself provides Hooks such as useState, useReducer, and so on. You get a custom Hook by calling these Hooks in a function named starting with “use”.
Custom Hooks allow usto wrap any logic into them to make it easy to reuse small enough component logic.
Controlled Components
When we turn the state of HTML elements like
styled-components
A CSS in JS library that works well with React. It allows you to write styles using JS and compile them into pure CSS files.
All the styles in the code below are written using it. This can be skipped if you are not interested in the implementation of styles in your code.
Code implementation
Input component
First we need to implement an Input component on which we will Input, validate, and prompt.
Input.js
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const Wrap = styled.div({
display: 'flex'.flexDirection: 'column'.label: { display: 'flex'.alignItems: 'center' },
input: {
marginLeft: 8,},p: {
color: 'red',}});function Input({ label, type, helperText, error, ... otherProps }) {
return (
<Wrap>
<label>
{label}:
<input {. otherProps} type={type} />
</label>
{error && <p>{helperText}</p>}
</Wrap>
);
}
Input.propTypes = {
label: PropTypes.string,
type: PropTypes.string,
helperText: PropTypes.string,
error: PropTypes.bool,
};
export default Input;
Copy the code
This component receives the following props:
label
Label Indicates the text of the labeltype
The type attribute assigned to the native input tagerror
The data type is Boolean iftrue
An error occurs in the current form field, that is, the authentication failshelperText
Prompt text displayed below the form field when the current form field cannot be authenticatedotherProps
All attributes in props other than the above four are assigned to the native Input tag
Custom Hook
With the UI component in place, we are ready to implement our custom hooks.
useInput.js
import { useState } from 'react'; export default function useInput({ initValue = '', helperText = '', validator = () => true, // Save the user input value and use initValue as the initial value const [value, setValue] = useState(initValue); Const [error, setError] = useState(false); function onChange(e) { const { value } = e.target; setValue(value); / / according to validateTriggers options, decide whether to check if in the onChange (validateTriggers. Includes (" onChange ")) {setError (! validator(value)); */ function createEventHandlers() {const eventHandlers = {}; ValidateTriggers. ForEach (item => {// Generate the appropriate event handler and do input validation in it. eventHandlers[item] = e => { const { value } = e.target; setError(! validator(value)); }; }); return eventHandlers; } const eventHandlers = createEventHandlers(); return { value, helperText, error, ... eventHandlers, onChange, }; }Copy the code
UseInput accepts an options object as a parameter. For extensibility, it is better to use a configuration object as a parameter.
The Options object has the following properties:
initValue
The initial value of the input boxhelperText
String to display when the form is not validatedvalidator
A function for form validation that takes value as an argument and returns a Boolean value indicating whether the form validation passedvalidateTriggers
An array of strings indicating which or more events to call the Validator for input validation.
In the function body, we call useState twice to initialize the values of value and error, holding the user-entered value and the validation result of the current form field, respectively.
We then declare an onChange method to bind the change event of the input element. In this method, we assign the value entered by the user to Value and decide whether to validate the input in the method using the values triggers from validateTriggers. This method is then returned and passed to the corresponding component as props to complete the state binding of the controlled component.
We also need to declare a createEventHandlers method, which generates the appropriate event handlers and validates the input in those event handlers by traversing the validateTriggers.
Finally, we call the createEventHandlers method and insert the generated eventHandlers (event handler) into the final returned object by extending the operator.
Note: We need to put onChange last here so that the onChange method with the state binding is not overwritten by the onChange in eventHandlers.
The specific use
Now let’s see how it works in practice:
import React from 'react';
import Input from './Input';
import useInput from './useInput';
// A regular expression used to validate mailboxes
const EMAIL_REG = /\S+@\S+\.\S+/;
export default function Form() {
const email = useInput({
initValue: ' '.helperText: 'Please enter a valid email! '.validator: value= > EMAIL_REG.test(value),
validateTriggers: ['onBlur']});const password = useInput({
initValue: ' '.helperText: 'Password length should be between 6-20! '.validator: value= > value.length >= 6 && value.length <= 20.validateTriggers: ['onChange'.'onBlur']});/** * Determine whether to disable the button */
function isButtonDisabled() {
// Disable button when the email address or password is not filled in, or the email address or password input fails verification
return! email.value || ! password.value || email.error || password.error; }/** * process form submission */
function handleButtonClick() {
console.log('Email:', email.value);
console.log('Password:', password.value);
}
return( <div> <Input {... Type ="email" /> <Input {... <button disabled={isButtonDisabled()} onClick={handleButtonClick}> </button> </div> ); }Copy the code
UseInput is called twice to initialize the email and Password form fields.
The extension operator is then used to assign all the values to the Input component. Isn’t it convenient to define initial values and bind controlled components in just a few lines of code?
The online operation
When we enter the mailbox, there is no verification prompt, but once we lose focus from the mailbox input box, the entered value is verified and the corresponding prompt is displayed based on the verification result. The password input field, on the other hand, is checked both during input and after out-of-focus.
conclusion
The above example handles basic form validation, but other requirements such as formatting user-input data and customizing when to collect values from a form field are left to your own design. This is where Hooks are special. They make it easier to reuse logic code by writing custom Hooks as needed.
The useInput API design in this article is just one of many, just for your reference. You can also encapsulate the state of the entire form into a useForm method that manages the state of all form fields uniformly.
I hope this article has given you some inspiration on how to use Hooks, which you are strongly encouraged to try, even if you have never used them before. I have used Hooks a lot in my project and it has worked well for me.