Introduction
In this article, you will learn how to create a custom hook in React 18.2.0 and call it using input components, and it will validate the data. Once data is validated, it will call the respective handler and update the states. In this article, we use UseReducer for state management.
Why do we require a custom hook?
- Custom hooks provide a simple alternative to Higher Order Component and Render props.
- Custom hook provides less repetitive code.
- Custom hook also provides fewer keystrokes.
Topics Covered
This article demonstrates how to build the following,
- Create a custom hook using React 18.2.0.
- Create a sample project to consume the custom hook.
- Testing
Pre-requisites
- Download and install React 18.2.0.
Tools
- React 18.2.0
Task 1. Create a sample react-js application
In this task, you will see how to create a sample react-js application using visual studio code.
Step 1
Open visual studio code and run the command in the terminal.
Step 2
Once the project is created successfully.
Go inside the folder.
And run the project
Step 3
To check default project is running successfully. Consider the below screen.
Once the project runs successfully below screen will appear.
Task 2. Create a new folder like "CustomHook" and add a component in it 'use-input.js'
In this task, you will see how to create a component inside a folder.
Step 1
Create a new folder inside src => with the name "CustomHook" and a component.
Don't confuse between my project and folder name. That's why my project name is custohook ('m' Missing), and folder name is 'CustomHook'
Step 2
Add a component in this folder with the name use-input.js. As per standard, when we create any custom hook, always start's the name using the 'use' keyword.
Step 3
Put the below code in this file. In the below code, we accept the validation condition as an input parameter, and the output will be an object in which the component will pass/call two handlers.
valueChangedHandler,
inputBlurHandler
Those will call and execute at parent components, events will be 'onChange' and 'onBlur'.
With few properties
value: enteredValue,
isValid: valueIsValid,
hasError,
These properties will be used to validate the parent component controls 'values, and the Last property will be used to reset the default value.
reset
import { useReducer } from "react";
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 {
value: state.value,
isTouched: true
};
}
if (action.type === 'RESET') {
return {
value: initialInputState.value,
isTouched: initialInputState.isTouched
}
}
return initialInputState;
}
export const useInput = (validateValue) => {
const [inputState, dispatchAction] = useReducer(inputStateReducer, initialInputState);
const valueIsValid = validateValue(inputState.value);
const hasError = !validateValue && inputState.isTouched;
const valueChangedHandler = (event) => {
dispatchAction({
type: 'INPUT',
value: event.target.value
});
};
const inputBlurHandler = () => {
dispatchAction({
type: 'BLUR'
});
};
const reset = () => {
dispatchAction({
type: 'RESET'
});
};
return {
value: inputState.value,
isValid: valueIsValid,
hasError,
valueChangedHandler,
inputBlurHandler,
reset,
};
}
Note: Custom control has one input property, and after validation, either success/failure, it will share the response.
Step 4
Create another component with the name "BaseForm".
In which the user has two input fields, using a custom component system can validate the input field response and showcase the proper error or success.
Note: I keep two input controls for my example, but it can be as many as you require.
Step 5
Add the below line of code to create the Email and Name input field with an error message, which will show based on the conditions.
export function BaseForm() {
const handleSubmit = async (event) => {
event.preventDefault();
};
return (
<body>
<h3>Custom Hook</h3>
<table border='1px solid'>
<tr>
<td>Please enter the Email</td>
<td>
<input
id="email"
type="email"
></input>
</td>
</tr>
<tr>
<td colSpan='2'>
{!IsEmailValid && (
<p className="invalidInput">Please enter a valid email</p>
)}
</td>
</tr>
<tr>
<td>Please enter the Name</td>
<td>
<input
id="name"
type="text"
></input>
</td>
</tr>
<tr>
<td colSpan='2'>
Name can't be empty
</td>
</tr>
<tr>
<td colSpan="3">
<button className="submitBtn" onClick={handleSubmit}>
Submit
</button>
</td>
</tr>
</table>
</body>
);
}
Consume Custom Control
Step 1
Import the custom control in baseform control.
Step 2
Modify input control and add handlers that will call when users type any value in it.
The name can be anything as your wish
Step 3
Consume/call the custom control. The user must validate the same or different logic based on the requirement.
For Name: The user must enter the value, and before sending it to custom control, we are using the Trim function to remove the blank space.
For Email: The user must enter a valid email or not, and custom control will validate it.
The complete code will be this.
import { useInput } from "./CustomHook/use-input";
export function BaseForm() {
const {
value: enterdName,
hasError: nameHasError,
isValid: IsNameValid,
valueChangedHandler: handleValueChange,
inputBlurHandler: handleBlueChange,
reset: handlerReset,
} = useInput((value) => value.trim() === "");
const {
value: enterdEmail,
hasError: emailHasError,
isValid: IsEmailValid,
valueChangedHandler: handleEmailChange,
inputBlurHandler: handleBlurEmailChange,
reset: handlerEmailReset,
} = useInput((value) => value.includes("@"));
const handleSubmit = async (event) => {
event.preventDefault();
console.log("Handle Submit Called");
console.log("Email value : " + event.target.email.value);
console.log("Name value : " + event.target.name.value);
handlerReset();
handlerEmailReset();
};
return (
<body>
<h3>Custom Hook</h3>
<form onSubmit={handleSubmit}>
<table border="1px solid">
<tr>
<td>Please enter the Email</td>
<td>
<input
id="email"
type="email"
onChange={handleEmailChange}
onBlur={handleBlurEmailChange}
value={enterdEmail}
></input>
</td>
</tr>
<tr>
<td colSpan="2">
{!IsEmailValid && (
<p className="invalidInput">Please enter a valid email</p>
)}
</td>
</tr>
<tr>
<td>Please enter the Name</td>
<td>
<input
id="name"
type="text"
onChange={handleValueChange}
onBlur={handleBlueChange}
value={enterdName}
></input>
</td>
</tr>
<tr>
<td colSpan="2">
{IsNameValid && (
<p className="invalidInput">Name can't be empty</p>
)}
</td>
</tr>
<tr>
<td colSpan="3">
<button className="submitBtn" >
Submit
</button>
</td>
</tr>
</table>
</form>
</body>
);
}
Output
Execute the code using.
npm start and enter a valid email address and name
Click on submit and consider the console output. And it will automatically blank the value using the reset property method.
Handle Submit Called
BaseForm.js:25 Email value : [email protected]
BaseForm.js:26 Name value : Piyush
Now enter the invalid email and keep the name blank.
It will show the error.
So, we have seen how much we have minimized the code and reduced code repetition. This way, we can write custom hooks that prevent code repetition and provide code minimization. We can write custom hooks for many implementations like generating sliders, generalizing functions, or any task implementation.