Preventing double clicks in React, with Hooks
5 August 2024
This is a follow up to my previous post, about preventing double clicks in React, this time using React Hooks.
When building interactive web applications, it's crucial to prevent users from accidentally submitting forms or triggering actions multiple times. One effective and user-friendly approach is to disable the button immediately after it's clicked.
The Problem
Consider a form submission button:
function SubmitButton() {
const handleSubmit = () => {
// Perform some action, e.g., API call
console.log("Form submitted!");
};
return <button onClick={handleSubmit}>Submit</button>;
}
If a user clicks this button twice quickly, the handleSubmit
function will be called twice, potentially causing duplicate form submissions or API calls.
We can prevent double clicks by disabling the button immediately after it's clicked. Here's how we can implement this using React hooks:
import React from "react";
function submitForm() {
// Simulating an async action that returns a Promise
return new Promise((resolve) => setTimeout(resolve, 1000));
}
function SubmitButton() {
const [isSubmitting, setIsSubmitting] = React.useState(false);
const handleSubmit = async () => {
if (isSubmitting) {
return; // Prevent double submission
}
setIsSubmitting(true);
try {
// Perform some action, e.g., API call
await submitForm();
console.log("Form submitted successfully!");
} catch (error) {
console.error("Error submitting form:", error);
} finally {
setIsSubmitting(false);
}
};
return (
<button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
);
}
Let's break down this implementation:
- We use the
useState
hook to create anisSubmitting
state variable, this lets us track when some background action is being run. - The
handleSubmit
function:- Checks if the form is already submitting and returns early if it is.
- Sets
isSubmitting
totrue
before starting the submission process. - Uses a try/catch block to handle the submission and any potential errors.
- Sets
isSubmitting
back tofalse
in thefinally
block, ensuring the button is re-enabled even if an error occurs.
- The button's
disabled
prop is tied to theisSubmitting
state, so that the user can't click it again to trigger the action. - The button text changes to provide feedback to the user.
We can also take this a step further, and create our own hook to extract this logic.
import React from 'react';
function submitForm() {
// Simulating an async action that returns a Promise
return new Promise(resolve => setTimeout(resolve, 1000))
}
export function useIsSubmitting(action): [loading, action] {
const [loading, setLoading] = React.useState(false);
function doAction(...x) {
setLoading(true);
return action(...x).finally(() => setLoading(false));
}
return [loading, doAction];
}
function SubmitButton() {
const [isSubmitting, doHandleSubmit] = useIsSubmitting(submitForm);
return (
<button onClick={doHandleSubmit} disabled={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
);
}
The final component doesn't need to handle any logic for reseting isSubmitting
, and can focus on rendering the different states.
Conclusion
Disabling the button after it's clicked is a simple yet effective way to prevent double clicks and double submissions in React applications. This approach provides clear visual feedback to the user and ensures that actions are only triggered once, even if the user accidentally clicks multiple times. By combining this with proper error handling and timeouts, you can create a robust and user-friendly interface for your forms and action buttons.