Introduction:
In the dynamic world of web applications, providing a seamless user experience is crucial. One aspect often overlooked is handling unsaved changes when a user navigates away from a page. In this article, we'll explore a custom React hook, usePrompt
, which leverages the power of React Router DOM's useBlocker
and a custom confirm popup to create a robust solution for preventing users from accidentally leaving a page with unsaved changes.
Part 1: Introducing the usePrompt Hook
Motivation:
Unsaved changes can lead to data loss and a frustrating user experience. TheusePrompt
hook addresses this challenge by integrating with React Router DOM'suseBlocker
and a custom confirmation popup.Key Features:
✅ React Router DOM Integration: Leveraging the useBlocker hook ensures that the user is prompted when attempting to navigate away from a page with unsaved changes.
✅ Custom Confirm Popup with useConfirm: Utilizing a custom confirmation popup through the useConfirm hook allows for a personalized and visually consistent user experience.
✅ Configurability: The hook is highly configurable, allowing developers to customize the appearance and behavior of the confirmation dialog.
The usePrompt
hook is designed to be a flexible and reusable solution for handling unsaved changes in a React application. Let's break down its key features:
import { useCallback, useEffect } from 'react';
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
import { useConfirm } from '../utils/confirm/confirm.hooks';
import { ConfirmOptions } from '../utils/confirm/confirm.types';
export const usePrompt = ({
isDirty = false,
title = 'You have unsaved changes!',
subtitle = 'Are you sure you want to leave?',
confirmText = 'Leave',
cancelText = 'Stay',
onConfirm,
onCancel,
type = 'warning',
}: ConfirmOptions & { isDirty?: boolean }) => {
const blocker = useBlocker(isDirty);
const { show } = useConfirm();
const confirm = useCallback(() => {
if (!isDirty) return Promise.resolve(true);
return new Promise<boolean>((resolve) => {
show({
title,
subtitle,
confirmText,
cancelText,
type,
onConfirm: () => {
resolve(true);
onConfirm?.();
},
onCancel: () => {
resolve(false);
onCancel?.();
},
});
});
}, [
cancelText,
confirmText,
isDirty,
onCancel,
onConfirm,
show,
subtitle,
title,
type,
]);
useEffect(() => {
if (blocker.state === 'blocked') {
confirm().then((result) => {
if (result) blocker.proceed();
else blocker.reset();
});
}
}, [blocker, confirm]);
useEffect(() => {
if (isDirty) {
window.onbeforeunload = () => subtitle;
}
return () => {
window.onbeforeunload = null;
};
}, [isDirty, subtitle]);
return {
confirm,
};
};
React Router DOM Integration with useBlocker:
The useBlocker
hook from React Router DOM is a crucial part of this solution. It ensures that the user is prompted when they attempt to navigate away from a page with unsaved changes. By leveraging useBlocker
, the hook seamlessly integrates with React Router DOM, making it an ideal choice for handling route navigation.
Configurable Options:
The usePrompt hook is highly configurable, allowing developers to customize the appearance and behavior of the confirmation dialog. Options include specifying whether the page has unsaved changes (isDirty)
, customizing the popup's title, subtitle, confirm and cancel text, and providing callbacks for confirmation and cancellation actions.
Part 2: The ConfirmProvider Component
Purpose:
TheConfirmProvider
component serves as the orchestrator for managing confirmation dialogs usingReact Context
. It encapsulates the state and logic required for displaying confirmation popups across the application.Key Components:
✅ Context Creation with createContext: The ConfirmCtx
context is created using createContext
to share confirmation dialog information.
✅ State Management with useState: The component manages the state of confirmation dialogs using useState
for confirm options and the visibility (open)
of the confirmation popup.
✅ Displaying Confirmation Popups with Confirm Component: The Confirm component is rendered within ConfirmProvider
to display confirmation dialogs based on the provided options.
When it comes to creating a smooth and intuitive user interface, having a well-designed confirmation popup can make all the difference. In this article, we'll explore the ConfirmProvider
component, a crucial part of a React application that enables the seamless display of confirmation popups through the use of React Context.
// ./utils/confirm/confirm.provider.tsx
import {
createContext,
type ReactNode,
useCallback,
useMemo,
useState,
} from 'react';
import { Confirm } from './confirm';
import { ConfirmContext, ConfirmOptions, Nullable } from './confirm.types';
export const ConfirmCtx = createContext<Nullable<ConfirmContext>>(null);
interface Props {
children: ReactNode;
}
export function ConfirmProvider({ children }: Props) {
const [confirm, setConfirm] = useState<Nullable<ConfirmOptions>>(null);
const [open, toggle] = useState(false);
const show = useCallback(
(confirmOptions: Nullable<ConfirmOptions>) => {
setConfirm(confirmOptions);
toggle(true);
},
[toggle]
);
const onConfirm = () => {
confirm?.onConfirm?.();
toggle(false);
};
const onCancel = () => {
confirm?.onCancel?.();
toggle(false);
};
const value = useMemo(() => ({ show }), [show]);
return (
<ConfirmCtx.Provider value={value}>
<Confirm
type="warning"
{...confirm}
onCancel={onCancel}
onConfirm={onConfirm}
open={open}
/>
{children}
</ConfirmCtx.Provider>
);
}
Understanding ConfirmProvider
The ConfirmProvider component is designed to manage the state of confirmation popups within a React application. It leverages the power of React Context to provide a centralized way of triggering and handling confirmation dialogs. Let's dive into its key features:
The ConfirmContext Type:
The ConfirmContext
type is an interface that defines the shape of the context value. It contains a single function, show
, which is responsible for displaying the confirmation popup. The show
function takes a ConfirmOptions
object as an argument and returns a promise that resolves to a boolean indicating whether the user confirmed the action or not.
export interface ConfirmOptions {
title?: string;
subtitle?: string;
confirmText?: string;
cancelText?: string;
onConfirm?: () => void;
onCancel?: () => void;
type?: 'success' | 'error' | 'warning' | 'info';
}
export type Nullable<T> = T | null;
export interface ConfirmContext {
show: (options: Nullable<ConfirmOptions>) => void;
}
Context Creation with createContext:
The ConfirmCtx
is a context created using createContext
from the React library. This context holds the information about the confirmation dialog and provides a way to share this information with the rest of the application.
export const ConfirmCtx = createContext<Nullable<ConfirmContext>>(null);
State Management with useState:
The component uses the useState hook to manage the state of the confirmation dialog. The confirm
state holds the configuration options for the confirmation popup, such as title, subtitle, confirm text, and cancel text. The open
state is a boolean indicating whether the confirmation dialog should be visible.
const [confirm, setConfirm] = useState<Nullable<ConfirmOptions>>(null);
Handling Confirmation and Cancellation:
The onConfirm
and onCancel
functions are callbacks that are triggered when the user confirms or cancels the action in the confirmation popup. These functions execute the corresponding actions specified in the confirmation options and then close the confirmation dialog by toggling the open
state.
const onConfirm = () => {
confirm?.onConfirm?.();
toggle(false);
};
const onCancel = () => {
confirm?.onCancel?.();
toggle(false);
};
Providing Context Value with useMemo:
The value prop provided to the ConfirmCtx.Provider is memoized using the useMemo
hook. This ensures that the context value only changes when the show function (responsible for displaying the confirmation popup) changes. This optimization prevents unnecessary re-renders of components consuming the context.
const value = useMemo(() => ({ show }), [show]);
Understanding useConfirm Hook:
The useConfirm
hook is designed to be used within components to access the confirmation dialog functionalities provided by the ConfirmProvider
. Let's break down its essential features:
Accessing ConfirmContext with useContext:
The hook utilizes theuseContext
hook from React to access the confirmation context(ConfirmCtx)
. This context holds theshow
function, which is responsible for triggering the display of confirmation dialogs.Throwing an Error for Missing Provider:
To ensure theuseConfirm
hook is used within a component hierarchy that includes theConfirmProvider
, a check is implemented. If the context is not available (i.e.,ConfirmProvider
is not a parent), an error is thrown, guiding developers to include the necessary provider in the component tree.Returning Confirm Context:
If the context is available, the hook returns thecontext
, providing access to theshow
function. Developers can then use this function to display confirmation dialogs as needed in their components.
// ./utils/confirm/confirm.hooks.tsx
import { useContext } from 'react';
import { ConfirmCtx } from './confirm.provider';
export const useConfirm = () => {
const context = useContext(ConfirmCtx);
if (!context) {
throw new Error('useConfirm must be used within a ConfirmProvider');
}
return context;
};
Using ConfirmProvider in Your Application:
To integrate the ConfirmProvider
into your application, simply wrap it around the components that require confirmation dialogs. Here's an example of how you can use it:
import ReactDOM from 'react-dom/client';
import { ConfirmProvider } from './utils/confirm/confirm.provider';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<ConfirmProvider>
<App />
</ConfirmProvider>
);
Now, any component within the ConfirmProvider
will have access to the show
function from the context, enabling them to trigger confirmation popups with ease.
Part 3: The Confirm Component - Crafting Visual Dialogs
- Purpose:
The Confirm
component is designed to render visually appealing confirmation dialogs with icons, customizable colors, and seamless user interactions.
-
Key Features:
✅ Icons for Visual Representation: Utilizes SVG icons
(CloseIcon, WarningIcon, etc.)
to visually represent different confirmation types.
✅ Color Customization: Dynamically sets background, text, and icon colors based on the specified confirmation type.
✅ Handling User Actions: Provides buttons for confirmation and cancellation actions styled with dynamic colors.
✅ Customizable Content: Renders the title, subtitle, confirm text, and cancel text based on the provided options.
When it comes to creating a smooth and intuitive user interface, having a well-designed confirmation popup can make all the difference. In this article, we'll explore the Confirm
component, a crucial part of a React application that enables the seamless display of confirmation popups through the use of React Context.
// ./utils/confirm/confirm.tsx
/* eslint-disable jsx-a11y/label-has-associated-control */
/* eslint-disable max-len */
import React from 'react';
import type { ConfirmOptions } from './confirm.types';
type Props = ConfirmOptions & { open: boolean };
function CloseIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
fill="none"
height="24"
viewBox="0 0 36 36"
width="24"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9 9L27 27M9 27L27 9"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
function WarningIcon() {
return (
<svg
fill="none"
height="24"
viewBox="0 0 36 36"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 9.735L29.295 29.25H6.705L18 9.735ZM18 3.75L1.5 32.25H34.5L18 3.75Z"
fill="#ED6C02"
/>
<path d="M19.5 24.75H16.5V27.75H19.5V24.75Z" fill="#ED6C02" />
<path d="M19.5 15.75H16.5V23.25H19.5V15.75Z" fill="#ED6C02" />
</svg>
);
}
function ErrorIcon() {
return (
<svg
fill="none"
height="24"
viewBox="0 0 36 36"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 25.5H18.015M18 15V21M9.61808 31.5H26.3819C29.0544 31.5 30.3906 31.5 31.1744 30.938C31.8583 30.4476 32.3047 29.6925 32.4049 28.8569C32.5196 27.8993 31.8756 26.7285 30.5877 24.3868L22.2058 9.14696C20.8345 6.65373 20.1489 5.40711 19.2429 4.99469C18.4533 4.63523 17.5467 4.63523 16.7571 4.99469C15.8511 5.40711 15.1654 6.65372 13.7941 9.14695L5.41224 24.3868C4.12432 26.7285 3.48036 27.8993 3.59509 28.8569C3.69521 29.6925 4.14168 30.4476 4.82558 30.938C5.60935 31.5 6.94559 31.5 9.61808 31.5Z"
stroke="#D92D20"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
function SuccessIcon() {
return (
<svg
fill="none"
height="24"
viewBox="0 0 36 36"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M24.885 11.37L15 21.255L9.615 15.885L7.5 18L15 25.5L27 13.5L24.885 11.37ZM18 3C9.72 3 3 9.72 3 18C3 26.28 9.72 33 18 33C26.28 33 33 26.28 33 18C33 9.72 26.28 3 18 3ZM18 30C11.37 30 6 24.63 6 18C6 11.37 11.37 6 18 6C24.63 6 30 11.37 30 18C30 24.63 24.63 30 18 30Z"
fill="#2E7D32"
/>
</svg>
);
}
function InfoIcon() {
return (
<svg
fill="none"
height="24"
viewBox="0 0 36 36"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 12H18.015M18 16.5V24M31.5 18C31.5 25.4558 25.4558 31.5 18 31.5C10.5442 31.5 4.5 25.4558 4.5 18C4.5 10.5442 10.5442 4.5 18 4.5C25.4558 4.5 31.5 10.5442 31.5 18Z"
stroke="#026AA2"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
type ColorValues = {
bgColor: string;
textColor: string;
iconColor: string;
};
type NotOptional<T> = T extends undefined ? never : T;
const colorMap: Record<NotOptional<Props['type']>, ColorValues> = {
error: {
bgColor: '#FDEDED',
iconColor: '#D92D20',
textColor: '#5F2120',
},
info: {
bgColor: '#EDF7ED',
iconColor: '#026AA2',
textColor: '#026AA2',
},
success: {
bgColor: '#EDF7ED',
iconColor: '#2E7D32',
textColor: '#1E4620',
},
warning: {
bgColor: '#FFF4E5',
iconColor: '#ED6C02',
textColor: '#663C00',
},
};
const iconMap: Record<NotOptional<Props['type']>, React.ReactNode> = {
error: <ErrorIcon />,
info: <InfoIcon />,
success: <SuccessIcon />,
warning: <WarningIcon />,
};
export function Confirm({
open,
title,
subtitle,
cancelText,
confirmText,
onCancel,
onConfirm,
type = 'warning',
}: Props) {
return (
<dialog
className={`absolute top-0 left-0 z-50 flex-col items-center justify-center w-full h-full overflow-auto bg-black bg-opacity-50 ${
open ? 'flex' : 'hidden'
}`}
>
<div
className="flex flex-col items-center justify-between w-10/12 p-4 rounded-lg lg:w-5/12 h-48"
style={{
background: colorMap[type].bgColor,
}}
>
<header
className="flex items-center justify-between w-full pb-2 border-b-2"
style={{
color: colorMap[type].textColor,
borderColor: colorMap[type].textColor,
}}
>
<div className="flex flex-row items-center justify-center gap-x-4">
{iconMap[type]}
<label className="text-lg font-bold ">{title}</label>
</div>
<CloseIcon className="cursor-pointer" onClick={onCancel} />
</header>
<div className="flex items-center justify-center m-auto rounded-lg">
<label
className="text-sm text-center"
style={{
color: colorMap[type].textColor,
}}
>
{subtitle}
</label>
</div>
<div className="flex items-center justify-center gap-x-4">
<button
onClick={onCancel}
style={{
background: 'gray',
}}
type="button"
>
{cancelText}
</button>
<button
onClick={onConfirm}
style={{
background: colorMap[type].iconColor,
}}
type="button"
>
{confirmText}
</button>
</div>
</div>
</dialog>
);
}
Understanding Confirm
The Confirm component is designed to render visually appealing confirmation dialogs with icons, customizable colors, and seamless user interactions. Let's dive into its key features:
Icons for Visual Representation:
The Confirm component utilizes SVG icons (CloseIcon, WarningIcon, etc.)
to visually represent different confirmation types. These icons are rendered based on the specified confirmation type, ensuring that the user can easily identify the type of action they are confirming.
type Props = ConfirmOptions & { open: boolean };
Color Customization:
The Confirm component dynamically sets background, text, and icon colors based on the specified confirmation type. This ensures that the confirmation dialogs are visually consistent with the rest of the application.
const colorMap: Record<NotOptional<Props['type']>, ColorValues> = {
error: {
bgColor: '#FDEDED',
iconColor: '#D92D20',
textColor: '#5F2120',
},
info: {
bgColor: '#EDF7ED',
iconColor: '#026AA2',
textColor: '#026AA2',
},
success: {
bgColor: '#EDF7ED',
iconColor: '#2E7D32',
textColor: '#1E4620',
},
warning: {
bgColor: '#FFF4E5',
iconColor: '#ED6C02',
textColor: '#663C00',
},
};
Handling User Actions:
The Confirm component provides buttons for confirmation and cancellation actions styled with dynamic colors. This ensures that the user can easily identify the type of action they are confirming.
<div className="flex items-center justify-center gap-x-4">
<button
onClick={onCancel}
style={{
background: 'gray',
}}
type="button"
>
{cancelText}
</button>
<button
onClick={onConfirm}
style={{
background: colorMap[type].iconColor,
}}
type="button"
>
{confirmText}
</button>
Customizable Content:
The Confirm component renders the title, subtitle, confirm text, and cancel text based on the provided options. This ensures that the confirmation dialogs are personalized and provide the user with the necessary information to make an informed decision.
<div className="flex flex-col items-center justify-between w-10/12 p-4 rounded-lg lg:w-5/12 h-48">
<header
className="flex items-center justify-between w-full pb-2 border-b-2"
style={{
color: colorMap[type].textColor,
borderColor: colorMap[type].textColor,
}}
>
<div className="flex flex-row items-center justify-center gap-x-4">
{iconMap[type]}
<label className="text-lg font-bold ">{title}</label>
</div>
<CloseIcon className="cursor-pointer" onClick={onCancel} />
</header>
<div className="flex items-center justify-center m-auto rounded-lg">
<label
className="text-sm text-center"
style={{
color: colorMap[type].textColor,
}}
>
{subtitle}
</label>
</div>
<div className="flex items-center justify-center gap-x-4">
<button
onClick={onCancel}
style={{
background: 'gray',
}}
type="button"
>
{cancelText}
</button>
<button
onClick={onConfirm}
style={{
background: colorMap[type].iconColor,
}}
type="button"
>
{confirmText}
</button>
</div>
</div>
Part 4: The useConfirm Hook - Bridging Components
Purpose:
TheuseConfirm
hook acts as a bridge, allowing components to easily access and trigger confirmation dialogs.Key Features:
✅ Accessing ConfirmContext with useContext: Utilizes the useContext hook to access the confirmation context (ConfirmCtx).
✅ Throwing an Error for Missing Provider: Throws an error if the context is not available, guiding developers to include the necessary ConfirmProvider in the component tree.
✅ Returning Confirm Context: Returns the context (including the show function) if available, enabling components to trigger confirmation dialogs.
When it comes to creating a smooth and intuitive user interface, having a well-designed confirmation popup can make all the difference. In this article, we'll explore the useConfirm
hook, a crucial part of a React application that enables the seamless display of confirmation popups through the use of React Context.
// ./utils/confirm/confirm.hooks.tsx
import { useContext } from 'react';
import { ConfirmCtx } from './confirm.provider';
export const useConfirm = () => {
const context = useContext(ConfirmCtx);
if (!context) {
throw new Error('useConfirm must be used within a ConfirmProvider');
}
return context;
};
Understanding useConfirm
The useConfirm
hook acts as a bridge, allowing components to easily access and trigger confirmation dialogs. Let's dive into its key features:
Accessing ConfirmContext with useContext:
The hook utilizes the useContext
hook from React to access the confirmation context (ConfirmCtx)
. This context holds the show
function, which is responsible for triggering the display of confirmation dialogs.
import { useContext } from 'react';
import { ConfirmCtx } from './confirm.provider';
export const useConfirm = () => {
const context = useContext(ConfirmCtx);
if (!context) {
throw new Error('useConfirm must be used within a ConfirmProvider');
}
return context;
};
Throwing an Error for Missing Provider:
To ensure the useConfirm
hook is used within a component hierarchy that includes the ConfirmProvider
, a check is implemented. If the context is not available (i.e., ConfirmProvider
is not a parent), an error is thrown, guiding developers to include the necessary provider in the component tree.
Part 5: The usePropmt Hook Usage - Handling Unsaved Changes
The usePrompt
hook is designed to be a flexible and reusable solution for handling unsaved changes in a React application. Here is an example of how you can use it:
First Usage without Form
This example demonstrates how to use the usePrompt
hook to prevent users from accidentally leaving a page with unsaved changes. The usePrompt
hook is used within the HomePage
component to display a confirmation dialog when the user attempts to navigate away from the page with unsaved changes.
// ./pages/home/index.tsx
import { usePrompt } from '../../hooks/usePrompt';
export default function HomePage() {
usePrompt({
isDirty: true,
});
return <div>HomePage</div>;
}
Enhancing User Experience with usePrompt and Form Validation in React
In the realm of web development, creating forms that provide a smooth user experience is crucial. In this article, we'll explore how to integrate the usePrompt
hook into a form, ensuring that users are prompted before navigating away from the page when they have unsaved changes.
-
Overview:
The
usePrompt
hook, in combination with React'suseReducer
and form state management, can significantly enhance the user experience by preventing accidental navigation away from a page with unsaved changes.
/* eslint-disable no-alert */
/* eslint-disable jsx-a11y/label-has-associated-control */
import { ChangeEvent, FormEvent, useReducer, useState } from 'react';
import { usePrompt } from '../../hooks/usePrompt';
const FORM_ACTIONS = {
UPDATE_FIELD: 'UPDATE_FIELD',
RESET_FORM: 'RESET_FORM',
} as const;
interface FormState {
name: string;
email: string;
message: string;
}
type FormAction =
| { type: typeof FORM_ACTIONS.UPDATE_FIELD; field: string; value: string }
| { type: typeof FORM_ACTIONS.RESET_FORM };
const initialState: FormState = {
name: '',
email: '',
message: '',
};
const formReducer = (state: FormState, action: FormAction): FormState => {
switch (action.type) {
case FORM_ACTIONS.UPDATE_FIELD:
return { ...state, [action.field]: action.value };
case FORM_ACTIONS.RESET_FORM:
return initialState;
default:
return state;
}
};
export default function ContactPage() {
const [state, dispatch] = useReducer(formReducer, initialState);
const [isDirty, setIsDirty] = useState(false);
const handleInputChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
): void => {
const { name, value } = e.target;
setIsDirty(true);
dispatch({ type: FORM_ACTIONS.UPDATE_FIELD, field: name, value });
};
const handleSubmit = (e: FormEvent): void => {
e.preventDefault();
alert(JSON.stringify(state, null, 2));
setIsDirty(false);
dispatch({ type: FORM_ACTIONS.RESET_FORM });
};
usePrompt({
isDirty,
});
const canSubmit = state.name && state.email && state.message;
return (
<div className="w-6/12 m-auto h-full">
<form className="flex flex-col gap-y-2" onSubmit={handleSubmit}>
<div className="flex flex-col gap-x-1">
<label htmlFor="name">Name</label>
<input
className="border rounded-md p-2"
id="name"
name="name"
onChange={handleInputChange}
type="text"
value={state.name}
/>
</div>
<div className="flex flex-col gap-x-1">
<label htmlFor="email">Email</label>
<input
className="border rounded-md p-2"
id="email"
name="email"
onChange={handleInputChange}
type="email"
value={state.email}
/>
</div>
<div className="flex flex-col gap-x-1">
<label htmlFor="message">Message</label>
<textarea
className="border rounded-md p-2"
id="message"
name="message"
onChange={handleInputChange}
value={state.message}
/>
</div>
<div className="flex flex-row justify-between gap-x-2 items-center">
<div
className={`w-4 h-4 rounded-full ${
isDirty ? 'bg-red-500' : 'bg-green-500'
}`}
/>
<p>{isDirty ? 'Form is dirty' : 'Form is clean'}</p>
<button
className="bg-orange-500 text-white rounded-md p-2 disabled:opacity-50 disabled:cursor-not-allowed mr-0 ml-auto"
disabled={!canSubmit}
type="submit"
>
Submit
</button>
</div>
</form>
</div>
);
}
Form State Management:
We employ auseReducer
hook to manage the form state and handle updates to form fields efficiently. The form state includes fields for name, email, and message.usePrompt Integration:
TheusePrompt
hook is introduced to monitor the form'sisDirty
state, indicating whether there are unsaved changes. When the user attempts to navigate away, they are prompted to confirm if they have unsaved changes.Input Handling::
ThehandleInputChange
function is responsible for updating the form state when the user enters data into the form fields. It also sets theisDirty
state to true, indicating that there are unsaved changes.Form Submission:
ThehandleSubmit
function is responsible for handling form submissions. It alerts the form state and resets the form state andisDirty
state to their initial values.Visual Indicators:
TheisDirty
state is used to display visual indicators to the user, indicating whether the form has unsaved changes. A red dot is displayed when the form is dirty, and a green dot is displayed when the form is clean.Form Validation:
ThecanSubmit
variable is used to disable the submit button when the form is invalid. This ensures that the user cannot submit the form until all required fields are filled out.
Part 6: Conclusion
In this article, we explored the usePrompt
hook, a flexible and reusable solution for handling unsaved changes in a React application. We also explored how to integrate the usePrompt
hook into a form, ensuring that users are prompted before navigating away from the page when they have unsaved changes.
Top comments (0)