Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/serifcolakel/vite-react-template
A repository for React Template with Eslint, Prettier & Husky Setup.
https://github.com/serifcolakel/vite-react-template
Last synced: 6 days ago
JSON representation
A repository for React Template with Eslint, Prettier & Husky Setup.
- Host: GitHub
- URL: https://github.com/serifcolakel/vite-react-template
- Owner: serifcolakel
- Created: 2023-09-26T14:28:30.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2023-11-12T17:47:46.000Z (about 1 year ago)
- Last Synced: 2024-11-09T02:52:35.322Z (2 months ago)
- Language: TypeScript
- Size: 122 KB
- Stars: 1
- Watchers: 2
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Building a Robust Unsaved Changes Prompt with React and React Router DOM
[Full Code on Github](https://github.com/serifcolakel/vite-react-template)
## 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. The `usePrompt` hook addresses this challenge by integrating with React Router DOM's `useBlocker` 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:
```tsx
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((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:**
The `ConfirmProvider` component serves as the orchestrator for managing confirmation dialogs using `React 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.
```tsx
// ./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>(null);
interface Props {
children: ReactNode;
}export function ConfirmProvider({ children }: Props) {
const [confirm, setConfirm] = useState>(null);const [open, toggle] = useState(false);
const show = useCallback(
(confirmOptions: Nullable) => {
setConfirm(confirmOptions);
toggle(true);
},
[toggle]
);const onConfirm = () => {
confirm?.onConfirm?.();
toggle(false);
};const onCancel = () => {
confirm?.onCancel?.();
toggle(false);
};const value = useMemo(() => ({ show }), [show]);
return (
{children}
);
}
```### 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.
```tsx
export interface ConfirmOptions {
title?: string;
subtitle?: string;
confirmText?: string;
cancelText?: string;
onConfirm?: () => void;
onCancel?: () => void;
type?: 'success' | 'error' | 'warning' | 'info';
}export type Nullable = T | null;
export interface ConfirmContext {
show: (options: Nullable) => 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.
```tsx
export const ConfirmCtx = createContext>(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.
```tsx
const [confirm, setConfirm] = useState>(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.
```tsx
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.
```tsx
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 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.- 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.- Returning Confirm Context:
If the context is available, the hook returns the `context`, providing access to the `show` function. Developers can then use this function to display confirmation dialogs as needed in their components.```tsx
// ./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:
```tsx
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(
);
```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.
```tsx
// ./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) {
return (
);
}function WarningIcon() {
return (
);
}function ErrorIcon() {
return (
);
}function SuccessIcon() {
return (
);
}function InfoIcon() {
return (
);
}type ColorValues = {
bgColor: string;
textColor: string;
iconColor: string;
};type NotOptional = T extends undefined ? never : T;
const colorMap: Record, 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, React.ReactNode> = {
error: ,
info: ,
success: ,
warning: ,
};export function Confirm({
open,
title,
subtitle,
cancelText,
confirmText,
onCancel,
onConfirm,
type = 'warning',
}: Props) {
return (
{iconMap[type]}
{title}
{subtitle}
{cancelText}
{confirmText}
);
}
```### 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.
```tsx
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.
```tsx
const colorMap: Record, 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.
```tsx
{cancelText}
{confirmText}
```#### 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.
```tsx
{iconMap[type]}
{title}
{subtitle}
{cancelText}
{confirmText}
```## Part 4: The useConfirm Hook - Bridging Components
- **Purpose:**
The `useConfirm` 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.
```tsx
// ./utils/confirm/confirm.hooks.tsximport { 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.
```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;
};```
#### 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.
```tsx
// ./pages/home/index.tsximport { usePrompt } from '../../hooks/usePrompt';
export default function HomePage() {
usePrompt({
isDirty: true,
});return
HomePage;
}
```### 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's `useReducer` and form state management, can significantly enhance the user experience by preventing accidental navigation away from a page with unsaved changes.```tsx
/* 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
): 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 (
Name
Message
{isDirty ? 'Form is dirty' : 'Form is clean'}
Submit
);
}
```- **Form State Management:**
We employ a `useReducer` 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:**
The `usePrompt` hook is introduced to monitor the form's `isDirty` 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::**
The `handleInputChange` function is responsible for updating the form state when the user enters data into the form fields. It also sets the `isDirty` state to true, indicating that there are unsaved changes.- **Form Submission:**
The `handleSubmit` function is responsible for handling form submissions. It alerts the form state and resets the form state and `isDirty` state to their initial values.- **Visual Indicators:**
The `isDirty` 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:**
The `canSubmit` 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.
## Part 7: Resources
- [React Router DOM](https://reactrouter.com/en/6.18.0/start/overview)
- [React useReducer Hook](https://react.dev/reference/react/useReducer)
- [React useState Hook](https://react.dev/reference/react/useState)
- [React useContext Hook](https://react.dev/reference/react/useContext)
- [React useMemo Hook](https://react.dev/reference/react/useMemo)