Form Submission and Validation - anastasiamexa/react-complete-guide-course-resources GitHub Wiki
Using useState
and FormData
in React forms is considered good practice because they provide a clean and efficient way to manage state and handle form data. Let's explore the reasons why these are considered beneficial:
1. State Management with useState:
-
Declarative State Updates:
useState
allows you to declare state variables and provides a way to update them. This promotes a declarative approach to managing state, making it easier to reason about the application's behavior. -
Functional Updates:
useState
provides a functional way to update state based on the previous state. This is particularly useful when dealing with asynchronous state updates or when the new state depends on the previous state. - Component Re-rendering: State changes trigger a re-render of the component, ensuring that the UI reflects the current state of the application. This is essential for building dynamic and responsive user interfaces.
2. Handling Form Data with FormData:
-
Easy Form Data Collection:
FormData
simplifies the process of collecting form data. It automatically captures form fields and their values, including file inputs and multiple selections. -
Dynamic Form Structure: With
FormData
, you don't need to manually update the form data structure when adding or removing form fields. It adapts dynamically to changes in the form, reducing maintenance efforts. -
Binary Data Support:
FormData
supports the inclusion of binary data, making it suitable for forms that involve file uploads. This is essential for handling file input fields.
3. Separation of Concerns:
-
Clear Separation of UI and State Logic: Using state management with
useState
promotes a clear separation of concerns. The UI components focus on rendering the user interface, while state logic resides in the component's functional code. -
Decoupling UI Logic from Data Handling: The use of
FormData
decouples the logic for handling form data from the intricacies of the DOM. This separation enhances code readability and maintainability.
4. Scalability and Maintainability:
-
Readability and Maintainability: The combination of
useState
andFormData
leads to cleaner and more readable code. This is particularly beneficial as the complexity of the form and its data handling logic increases. -
Adaptability to Changes: The use of state and
FormData
facilitates easy adaptation to changes in form requirements or structure. As new form fields are added or existing ones are modified, the code can remain concise and adaptable.
In summary, using useState
for state management and FormData
for handling form data aligns well with React's principles of declarative programming, simplicity, and separation of concerns. These practices contribute to more maintainable, scalable, and readable code in React applications.
import { useState } from "react";
export default function Signup() {
const [passwordsAreNotEqual, setPasswordsAreNotEqual] = useState(false);
// Handle the form submission using FormData
function handleSubmit(event) {
event.preventDefault();
// name must have been set on the input elements
const formData = new FormData(event.target);
// Multiple values can be selected for the same name
const acquisitionChannel = formData.getAll('acquisition');
const data = Object.fromEntries(formData.entries());
// Add the acquisition channel to the data object
data.acquisition = acquisitionChannel;
if (data.password !== data['confirm-password']) {
setPasswordsAreNotEqual(true);
return;
}
console.log(data);
// Reset the form
//event.target.reset();
}
return (
<form onSubmit={handleSubmit}>
<h2>Welcome on board!</h2>
<p>We just need a little bit of data from you to get you started 🚀</p>
<div className="control">
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" required />
</div>
<div className="control-row">
<div className="control">
<label htmlFor="password">Password</label>
<input id="password" type="password" name="password" required minLength={6} />
</div>
<div className="control">
<label htmlFor="confirm-password">Confirm Password</label>
<input
id="confirm-password"
type="password"
name="confirm-password"
required
/>
<div className="control-error">
{passwordsAreNotEqual && 'The passwords do not match'}
</div>
</div>
</div>
</form>
);
}
The following example represents a login form component in React, and it utilizes a reusable custom hook named useInput
for managing the state and validation of input fields. Let's break down the code and explain it step by step:
// useInput.js
import { useState } from 'react';
export function useInput(defaultValue, validationFn) {
const [enteredValue, setEnteredValue] = useState(defaultValue);
const [didEdit, setDidEdit] = useState(false);
const valueIsValid = validationFn(enteredValue);
function handleInputChange(event) {
setEnteredValue(event.target.value);
setDidEdit(false);
}
function handleInputBlur() {
setDidEdit(true);
}
return {
value: enteredValue,
handleInputChange,
handleInputBlur,
hasError: didEdit && !valueIsValid
};
}
Explanation:
-
useInput
is a custom hook that takes an initialdefaultValue
and avalidationFn
function as parameters. -
enteredValue
state variable holds the current value of the input field. -
didEdit
state variable tracks whether the input has been edited (blurred) or not. -
valueIsValid
is a boolean indicating whether the current value is valid according to the provided validation function. -
handleInputChange
is a function that updates theenteredValue
when the input value changes. It also resets thedidEdit
flag tofalse
. -
handleInputBlur
is a function that sets thedidEdit
flag totrue
when the input field is blurred. - The hook returns an object with properties:
-
value
: the current value of the input. -
handleInputChange
: a function to handle input changes. -
handleInputBlur
: a function to handle input blur events. -
hasError
: a boolean indicating whether the input has an error based on validation.
-
Example of usage in a Login component:
// Login.jsx
import Input from './Input.jsx';
import { isEmail, isNotEmpty, hasMinLength } from '../util/validation.js';
import { useInput } from '../hooks/useInput.js';
export default function Login() {
const {
value: emailValue,
handleInputChange: handleEmailChange,
handleInputBlur: handleEmailBlur,
hasError: emailHasError,
} = useInput('', (value) => isEmail(value) && isNotEmpty(value));
const {
value: passwordValue,
handleInputChange: handlePasswordChange,
handleInputBlur: handlePasswordBlur,
hasError: passwordHasError,
} = useInput('', (value) => hasMinLength(value, 6));
function handleSubmit(event) {
event.preventDefault();
if (emailHasError || passwordHasError) {
return;
}
console.log(emailValue, passwordValue);
}
return (
<form onSubmit={handleSubmit}>
<h2>Login</h2>
<div className="control-row">
<Input
label="Email"
id="email"
type="email"
name="email"
onBlur={handleEmailBlur}
onChange={handleEmailChange}
value={emailValue}
error={emailHasError && 'Please enter a valid email!'}
/>
<Input
label="Password"
id="password"
type="password"
name="password"
onChange={handlePasswordChange}
onBlur={handlePasswordBlur}
value={passwordValue}
error={passwordHasError && 'Please enter a valid password!'}
/>
</div>
<p className="form-actions">
<button className="button button-flat">Reset</button>
<button className="button">Login</button>
</p>
</form>
);
}
Explanation:
- The
Login
component imports theuseInput
hook and validation functions from external sources. - Two instances of the
useInput
hook are used to manage the state and validation of the email and password input fields. -
handleSubmit
function is triggered on form submission. It checks if there are any validation errors before logging the email and password values to the console. - The component renders a form with email and password input fields, utilizing the
Input
component (which is assumed to handle rendering input fields consistently). - Validation errors are displayed beneath the respective input fields.
In summary, this code illustrates a modular and reusable approach to handling input state and validation in a login form using a custom hook (useInput
). The separation of concerns between the Login
component and the useInput
hook makes the code more maintainable and easy to understand.