Different Ways to Handle Form Submission in React
Much like HTML, in React, forms are also used to allow user interaction within web pages. Adding a form in React is as simple as adding any other element:
<form>
{/* Form fields go here */}
</form>
However, the default behaviour of form submission in React typically isn't what we want. Instead, we aim to override this default behaviour and allow React to manage the form. This gives us more control over how the form data changes and how it is submitted.
There are two common approaches used in React when building forms: Controlled and uncontrolled forms.
Controlled forms
In a controlled form, the values of the form fields are controlled by the React component, usually stored in the component's state. This means that every change of a value in the field triggers a state update, which then re-renders the component with the updated value.
Controlling form data using the useState hook
The first approach that usually comes to mind when building controlled forms is using the component's state to store the values of the fields. Specifically, the useState hook (in functional components). Here is a simple example of how this looks within a small form with one input field:
import { useState } from "react";
function ControlledForm() {
const [name, setName] = useState("");
return (
<form>
<label>Enter your name:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
</form>
)
}
export default ControlledForm;
This approach might seem fine for simple forms, but as a form grows, the code can get more and more complex. Imagine if there are 15 fields in a form. There will have to be a useState hook for each of the 15 form fields. This will make maintaining the state and code harder.
In situations that require large and complex forms, using a different approach than the component's state is a better idea. Such an approach is using React's useReducer hook.
Controlling form data using the useReducer hook
In React, a reducer is a function that manages complex state logic in a more organized and predictable way. It takes the current state and an action and calculates the new state based on that action. It's like having a set of instructions for how to update your state depending on what action is performed.
The useReducer is a React hook that allows you to use reducers in functional components.
Let's improve the previous example to use useReducer instead of useState. First, we're going to define the initial state. Since we only have one field for now, we'll add a new state property values that will be an object with one property: name, which is the name of our form field.
In a new file that will hold the reducer function, we'll define and export our initial state:
export const initialState = {
values: {
name: ''
}
};
Next, we will define the reducer function. Since this is the simplest example, we'll add only one action that will describe what we want to change in the state. Let's name the action SET_VALUE. The task of this action will be to update the specific value of the 'name' field.
export function formReducer(state, action) {
switch (action.type) {
case 'SET_VALUE':
return {
...state,
values: {
...state.values,
[action.payload.field]: action.payload.value,
},
};
default:
return state;
}
}
If you're not familiar with how a reducer works, here is a brief explanation of the code:
- state (the current state of the form)
- action (an object describing what changes to make to the state)
case 'SET_VALUE': If the action type is 'SET_VALUE', the reducer will update the values object in the state.
return {...state, ...}: This line creates a new state object by copying the existing state.
values: {...state.values, ...}: This line creates a new values object by copying the current values from the state.
[action.payload.field]: action.payload.value: This updates a specific field in the values object. The field to update and the new value come from action.payload (the action payload is passed when the action is triggered from the form component. You'll see this later in this post).
default: If the action type is not 'SET_VALUE', the reducer just returns the current state without making any changes.
Next, we'll need to update the form component to use our new reducer to manage the form data instead of the useState hook.
import { useReducer } from 'react';
import { formReducer, initialState } from './reducer';
function ControlledForm() {
const [state, dispatch] = useReducer(formReducer, initialState);
return (
<form>
<label>Enter your name:
<input
type="text"
value={state.values.name}
onChange={(e) =>
dispatch({
type: 'SET_VALUE',
payload: { field: 'name', value: e.target.value },
})
}
/>
</label>
</form>
)
}
export default ControlledForm;
Now, let's break down the code. First, we import the reducer and initial state we defined in the previous step and pass them into the useReducer hook.
- state: will always hold the current state of the component
- dispatch: a function that is called to update the state. When dispatch is called with an action object, the formReducer function will process this action and return the new state
- type: the type of the action we want to trigger. In this case, it's the SET_VALUE action that we added in our reducer.
- payload: an object containing the data we want to update in our state. In this case, we're passing the name of the field we want to update and the value of the field that we want to update it to.
That's it! We've just updated our form to use useReducer, and now we can add a lot more form fields while keeping the code clean and easily maintainable.
We can also add new actions in our reducer, for example: an action that will keep info about which fields are touched, or an action that will handle errors in our form, etc.
Uncontrolled forms
Uncontrolled forms rely on the browser to manage the form data. In this approach, form fields maintain their own state, and React components only need to access the DOM elements to retrieve the data when needed.
Uncontrolled form using the useRef hook
When building uncontrolled forms, the useRef hook is often used to access the value of each form field. useRef is a hook that returns a mutable ref object. This object has a current property that can be used to store a value or a reference to a DOM element. Unlike state, updating a ref does not cause the component to re-render.
import { useRef } from 'react';
function UncontrolledForm() {
const nameRef = useRef();
return (
<form onSubmit={(e) => {
e.preventDefault();
alert(nameRef?.current.value);
}}>
<label>Enter your name:
<input ref={nameRef} type="text" />
</label>
</form>
);
}
export default UncontrolledForm;
This will work great for simpler forms. However, just like when using useState, this approach also becomes hard to maintain once the form grows and a lot more fields are needed. That's why I'd always recommend using the magical FormData object.
Uncontrolled form using the FormData object
The FormData object is a built-in JavaScript object that provides a way to easily construct a set of key/value pairs representing form fields and their values. It is commonly used to send form data. It is also important to note that FormData will only use input fields that use the name attribute.
Let's see this in code. First, we'll take the previous example and replace the input ref with a new ref for our form. We'll need this in order to use the FormData object on our form element.
import { useRef } from 'react';
function UncontrolledForm() {
const formRef = useRef();
...
}
export default UncontrolledForm;
Next, we'll define a new function that will handle the form submission.
import { useRef } from 'react';
function UncontrolledForm() {
const formRef = useRef();
const handleSubmit = (e) => {
// prevent the default submit behavior that will refresh the page
e.preventDefault();
// construct the key/value pairs from the form element.
const formData = new FormData(formRef.current);
const formValues = {};
// map each key/value pair to a new object
formData.forEach((value, key) => {
formValues[key] = value;
});
alert(JSON.stringify(formValues, null, 2));
};
...
}
export default UncontrolledForm;
When you create a new FormData object and pass a form element to it, it automatically collects all the data (field values) from that form.
The complete code looks like this:
import { useRef } from 'react';
function UncontrolledForm() {
const formRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(formRef.current);
const formValues = {};
formData.forEach((value, key) => {
formValues[key] = value;
});
alert(JSON.stringify(formValues, null, 2));
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<label>Enter your name:
<input name="name" type="text" />
</label>
<button type="submit">Submit</button>
</form>
);
}
export default UncontrolledForm;
That's it. You can now add any fields you like, and they will be automatically picked up by the FormData object as long as they have the name attribute.
Uncontrolled form using the new React form action attribute
Another way to build uncontrolled forms is using the new React extension to the form element, specifically the improved action attribute. Unlike the standard action attribute on the form element, the new improvements allow us to pass a URL or a function as an action. When a function is passed as action, the function will handle the form submission. It is called with a single argument containing the form data of the submitted form. The action prop can also be overridden by a formAction attribute on a <button>, <input type="submit">, or <input type="image"> component.
Let's see how it looks:
function UncontrolledFormActionAttribute() {
const handleSubmit = (formData) => {
const name = formData.get('name');
alert(name);
};
return (
<form action={handleSubmit}>
<label>Enter your name:
<br />
<input name="name" type="text" />
</label>
<button type="submit">Submit</button>
</form>
);
}
export default UncontrolledFormActionAttribute;
Note: At the moment of writing this post, React’s extensions to <form> are currently only available in React’s canary and experimental channels.
Which Approach to use
When deciding between controlled and uncontrolled forms, consider the complexity and requirements of your application. Controlled forms are generally recommended for most use cases due to the precise control they offer over form data. They are particularly useful when you need to validate form input, control complex user interactions, or synchronize form state with other parts of your application. On the other hand, uncontrolled forms can be a better choice for simple forms or when integrating third-party libraries where direct access to form elements is needed. They are easier to set up and require less boilerplate code.
For a balanced approach, you can use the FormFusion library, which combines the benefits of both methods. FormFusion uses the performant uncontrolled forms approach and takes the burden of validation, error handling, and other complexities off your hands. It also offers a controlled version using the useReducer hook under the hood, making it suitable for applications hat require more control over the form data.
Ultimately, for large-scale applications that require more control over the form data, using FormFusion with its controlled version is preferable, while for simpler forms or when additional control over the form data is not required, the uncontrolled forms version provided by FormFusion might be a better choice.
The code from this post can be found on Github.
Using enterkeyhint Attribute to Build Better Mobile Forms
— Do & Learn
If you're looking to improve how you build your mobile forms, you've come to the right place! In our previous post, "Inputmode Attribute Explained: Key to Better Mobile Forms", we explored how the inputmode attribute can improve input fields for mobile devices. Today, we're continuing that story with another valuable attribute: the enterkeyhint. This small yet powerful attribute can greatly enhance the user experience and accessibility of your forms on mobile devices. Let’s explore how to use this attribute and learn how to create more intuitive and user-friendly forms.What is enterkeyhint?The enterkeyhint is a global HTML attribute that allows developers to tell the users what action will occur when they press the enter key on a virtual keyboard, such as submitting a form, moving to the next input field or inserting a new line. While inputmode controls what kind of virtual keyboard will be shown, enterkeyhint indicates how the enter key will be labeled (or which icon will be shown).Providing a clear and relevant label or icon can make your form interactions smoother and more intuitive, especially on mobile devices where keyboard space is limited.How Does enterkeyhint Work?The enterkeyhint works on any form fields such as input, textarea or any other element that is editable (has contenteditable=true). It is an enumerated attribute and only accepts the following values:enterkeyhint="enter"Indicates that pressing the enter key on a virtual keyboard will insert a new line. The label shown depends on user agent and user language but it is typically something like: ↵ or 'Return'.For example, if you have a multi-line text input (like a textarea) and you set enterkeyhint="enter", the virtual keyboard will display the enter key with a label indicating that it performs a line break or simply inserts a new line, rather than performing a specific action such as form submission or search. enterkeyhint="done"Indicates that pressing the enter key on a virtual keyboard will complete the current action, usually submitting a form.This hint is typically used in forms where the user is expected to finish entering data and submit it. For example, if you have a text input field in a form, setting enterkeyhint="done" will display a "Done" label on the enter key of the virtual keyboard, signaling that pressing it will submit the form or complete the current input.enterkeyhint="go"Indicates to the user that pressing the enter key on a virtual keyboard will trigger a "go" action. This is typically used in contexts where the user is expected to submit a search query or initiate a navigation action.For example, if you have a search input field, setting enterkeyhint="go" would display a "Go" label on the enter key of the virtual keyboard, signaling to the user that pressing it will start the search process.enterkeyhint="next"Indicates that pressing the enter key on a virtual keyboard will move the focus to the next input field or element in the form rather than submitting the form or performing another action. It would display a "Next" label on the enter key.This makes the navigation between fields more intuitive for users.enterkeyhint="previous" Indicates that pressing the enter key on a virtual keyboard will move the focus to the previous input field or element in the form. It would display a "Previous" label on the enter key. This is probably not that commonly used like the "next" hint, however is can be useful in multi-field forms where users may need to navigate backward through fields they have already filled out. enterkeyhint="search" Indicates that pressing the enter key on a virtual keyboard will trigger a search action. This is useful for input fields where users are expected to enter search queries. By setting enterkeyhint="search", the virtual keyboard will display a label such as "Search" on the enter key.enterkeyhint="send" Indicates that pressing the enter key on a virtual keyboard will trigger a send action. It would display a "Send" label on the enter key. This is useful in contexts where the user is expected to send a message or submit a communication.If no enterkeyhint attribute is provided, the label on the enter key on the virtual keyboard defaults to the browser's or device's standard settings which may be generic, such as "Enter" or "Return" or if inputmode, type, or pattern attributes are used, the user agent might use contextual information from these attributes and display a suitable label or icon.Note: Applying the enterkeyhint attribute only informs the user what will happen on press on the Enter key. To fully improve the user experience and the accessibility of your forms you must also implement the sutable functionality such as automatically navigating though the form fields on keypress or submitting the form on press on Enter.ExamplesLet's see it in action. Bellow is a multi-step form with three simple fields and a button that is responsible for submitting the form. <form> <input type="text" enterkeyhint="next" placeholder="First name" /> <input type="text" enterkeyhint="next" placeholder="Last name" /> <input type="text" enterkeyhint="done" placeholder="Address" /> <button type="submit">Save</button> </form> As users fill out each field and press enter, they are guided through the form fields in order due to the enterkeyhint="next" attribute. When they reach the last field, the enterkeyhint="done" attribute signals that pressing on Enter key at this point will submit the form. Try out the example on your mobile device or using a virtual keyboard. Click on any field and notice the label in the right bottom corner. Save It should look something like this. (This example is on iOS, the keyboard shown will vary depending on what type of device is used). You can also apply this attribute dynamically using the enterKeyHint property of on a HTMLElement const firstName = document.getElementById("firstName"); const lastField = document.getElementById("lastName"); firstName.enterKeyHint = "Next"; lastField.enterKeyHint = "Done";When to Use enterkeyhintThe enterkeyhint attribute is useful when you want to control the label or icon displayed on the "enter" key on virtual keyboards. You should use it in cases where the default behaviour doesn't fully match the user experience you're trying to provide. Here are situations where you can use its benefits: Forms with Multiple Steps - As shown in the example above, when there are multiple fields or steps in a form and the functionality for field navigation is implemented, using enterkeyhint="next" or enterkeyhint="previous" can improve the user experience by informing them what will happen when they press Enter key. Search Fields - Probably the most common scenario is to apply enterkeyhint="search" on a search input field that performs a search on press on Enter key. Sending a Message- Similar to search fields, the attribute can be applied to an input or textarea fields that contain a message that will be sent on press on Enter key. Custom Actions - Sometimes, even though the browser can infer an appropriate label, you might want to override it for a better user experience. For example, on a numeric input field, the default key might be "Next," but you want the key to indicate "Go" or "Done."Browser supportenterkeyhint attribute is supported by most major browsers as shown on the table below. :root { --post-background-color: #2e5ba6; --post-content-color: #ffffff;} form.enterkeyhint-form-example { display: flex; flex-direction: column; gap: 1em; }
Privacy First: How to Securely Handle User Data in Forms
— Do & Learn
When working with forms that handle sensitive information like personal details, passwords, or financial data, paying attention to privacy is crucial. It’s about respecting your users and protecting their personal information. Every time you collect or process data through a form, you need to be clear about how you will use, store, or share this information. Here are some simple steps to help you build trust with your users.Limit Data Collection Only ask for the information you need to make your form work. For example, if you’re creating a signup form for a newsletter, you only need the user’s email address. Avoid requesting extra details such as phone numbers or addresses unless they are necessary. Similarly, for a contact form, asking for just a name and email address may be enough in most situations.Collecting only necessary data does not only reduces the risk of exposing personal information but also improves the user experience. Short, straightforward forms are easier to complete and less frustrating for users.Tip: Regularly check your forms to ensure they only collect necessary information. This keeps your forms user-friendly and helps protect personal data.Provide Clear Data Collection DetailsClearly inform users about what data you’re collecting and why. Make sure to always ask for their explicit permission before processing their sensitive information. Use clear labels: For example, instead of just a checkbox that says “I agree,” use labels like “I agree to receive emails at this address” Provide additional text: Include a brief description near the checkbox or consent request. For example, “We will use your email address only to send you updates and offers. You can unsubscribe at any time.” Highlight important details: If you are collecting sensitive information, make sure to explain why it’s needed and how it will be protected. For example, “We need your phone number to provide customer support. Your number will not be shared with third parties.”Tip: Make sure the consent request is easy to understand and does not use complicated legal terms. This helps users make informed decisions about their data.Bad approach: I agree Sign UpGood approach: I agree to receive newsletters at this email address. (We will use your email only to send updates and offers. You can unsubscribe at any time.) Sign UpHide/Mask Sensitive InformationFor fields that require sensitive information such as passwords, credit card numbers and CCV, use masking techniques to hide the user's input. This ensures that user's sensitive data is not exposed while they are typing. For example, show asterisks (****) instead of the actual characters in password fields, CVV or any other similar information.Bad approach: Password: CVV: SubmitGood approach: Password: CVV: SubmitAllow Data Access and UpdateGive users the option to view their data, make updates, or delete it if they want to. For example, if a user wants to change their email address, they should be able to update it easily in their account settings. This will help users feel in control of their personal information and build trust.Update Privacy Practices RegularlyStay up-to-date with changes in privacy laws, such as GDPR (General Data Protection Regulation) and CCPA (California Consumer Privacy Act). Regularly review and update your privacy practices to ensure compliance with these laws.Tip: Consider having a privacy expert review your policies periodically to ensure they meet legal requirements and best practices.ConclusionHandling user data with care is a legal requirement but also essential for building trust with your users. By following these steps, you can protect user privacy and make sure your forms are secure and trustworthy. Showing that you are committed to protecting personal information helps create a safe environment for your users, therefore increases your user acitvity and engagement. :root { --post-background-color: #005ae8; --post-content-color: #ffffff;}
Inputmode Explained: The Key to User-friendly Mobile Forms
— Do & Learn
If you’ve ever struggled with typing the right information into a web form on your phone, you’ll appreciate how much a simple change like inputmode can improve the experience. But what exactly is inputmode, and how can you use it to make your forms more user-friendly?What is Inputmode?inputmode is an HTML attribute that tells the browser which type of virtual keyboard to display when a user focuses on an input field. By showing the appropriate keyboard, it makes typing faster and reduces errors. It’s especially useful on mobile devices where keyboard layouts can change based on the type of data you need to enter. inputmode is primarily used on <input /> elements but it can also be applied on any other HTML element in contenteditable mode. How Does inputmode Work?When you add the inputmode attribute to an input field, you specify the type of data you expect users to enter. The browser then displays the most relevant keyboard. Here are all the values that inputmode can have: text: Default keyboard for text input. numeric: Number pad for numerical input. decimal: Number pad with a decimal point. tel: Telephone keypad for entering phone numbers. It includes the digits 0–9, the asterisk (*), and the pound (#) key email: Keyboard optimised for email addresses. Includes @ and .com buttons. url: Keyboard optimised for URLs (includes / and . buttons). search: Keyboard optimised for search input. For example, the return/submit key may be labeled "Search", along with possible other optimisations. none: No virtual keyboard. For when the page implements its own keyboard input control.ExamplesLet’s look at some examples of how to use inputmode in your forms:Numeric <label for="amount">Amount:</label> <input type="text" id="amount" inputmode="numeric" placeholder="Enter amount"/>The keypad on mobile will look something like this. (This can vary depending on the mobile OS, this example shows how it looks on iOS)Tel <label for="phone">Phone:</label> <input type="text" id="phone" inputmode="tel" placeholder="Enter your phone number"/>The keypad on mobile will look something like this:Email <label for="email">Email:</label> <input type="text" id="email" inputmode="email" placeholder="Enter your email"/>The keypad on mobile will look something like this:UrlHere is an example of using a <div> element in editable mode as a field: <label for="url">Url:</label> <div contenteditable="true" id="url" inputmode="url" placeholder="Enter a url"/>The keypad on mobile will look something like this:When to Use inputmodeIf you're using the HTML <input> element with a specific type attribute (other then 'text'), the keypad will be automatically shown depending on the type. However, if you need a more custom input or other editable element then you might need to use the proper inputmode. Using the correct inputmode will allow you to optimise the input experience for users, especially on mobile devices. Here are some situations where it can be helpful: Numeric: Credit Card Numbers Security Codes (e.g., CVV for credit cards) Bank Account Numbers Social Security Numbers Employee or Student ID Numbers Membership Numbers Serial Numbers for Products Decimal: Prices Weights Product Dimensions Coordinates (e.g., latitude and longitude) Financial Data (e.g., interest rates) Measurement Units (e.g., height, length) Tel: Phone Numbers Fax Numbers Email: Email Addresses Contact Forms URL: Website URLs API Endpoints Search: Search Fields on Websites Search Boxes in Applications Text: Names Addresses General Text Inputs By making these small adjustments, you can significantly improve the user experience on your web forms, making them quicker and easier to fill out.Browser supportinputmode attribute is supported by most major browsers as shown on the table below. :root { --post-background-color: #bee4da; }