Skip to content

Commit 85d4b21

Browse files
authored
Merge pull request #383 from timolins/multi-toaster
Add support for multiple toasters
2 parents 35f5efe + 15605ee commit 85d4b21

File tree

12 files changed

+549
-88
lines changed

12 files changed

+549
-88
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@
6161
},
6262
{
6363
"path": "dist/index.mjs",
64-
"limit": "5 KB"
64+
"limit": "5.5 KB"
6565
},
6666
{
6767
"path": "headless/index.js",
68-
"limit": "2 KB"
68+
"limit": "2.5 KB"
6969
},
7070
{
7171
"path": "headless/index.mjs",
72-
"limit": "2 KB"
72+
"limit": "2.5 KB"
7373
}
7474
],
7575
"devDependencies": {

site/components/docs-layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ export default function DocsLayout({ meta, children }) {
7575
</TableItem>
7676
<TableHeader>Guides</TableHeader>
7777
<TableItem href="/docs/styling">Styling</TableItem>
78+
<TableItem href="/docs/multi-toaster">Multi Toaster</TableItem>
79+
7880
<TableHeader>Releases</TableHeader>
7981
<TableItem href="/docs/version-2">New in 2.0</TableItem>
8082
</div>

site/pages/docs/multi-toaster.mdx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import Layout from '../../components/docs-layout';
2+
import toast, { Toaster } from 'react-hot-toast';
3+
4+
export const meta = {
5+
title: 'Multiple Toasters',
6+
};
7+
8+
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
9+
10+
# Multiple Toasters
11+
12+
React Hot Toast supports multiple toaster instances in your app, They can be used and configured independently of each other. This is useful for having notifications in different areas of your app.
13+
14+
You can use multiple toasters by creating a [`Toaster`](/docs/toaster) with a unique `toasterId`:
15+
16+
```jsx
17+
<Toaster toasterId="sidebar" />
18+
```
19+
20+
## Example
21+
22+
This example shows two toasters, each maintaining their own state and configuration.
23+
24+
<div className="not-prose flex gap-4 flex-col md:flex-row my-4">
25+
<div className="relative min-h-[200px] bg-toast-200 text-toast-800 rounded-lg p-4 overflow-hidden flex-1 flex flex-col gap-2">
26+
<p className="text-lg flex-1 text-center text-toast-300 flex items-center justify-center">Area 1</p>
27+
<Toaster
28+
toasterId="area1"
29+
position="top-center"
30+
containerStyle={{ position: 'absolute' }}
31+
/>
32+
<button
33+
onClick={() => toast('Notification for Area 1', { toasterId: 'area1' })}
34+
className="bg-toast-600 text-white px-4 py-2 rounded-lg hover:bg-toast-600 w-full"
35+
>
36+
Show Toast in Area 1
37+
</button>
38+
</div>
39+
40+
<div className="relative min-h-[200px] rounded-lg p-4 overflow-hidden flex-1 flex flex-col gap-2" style={{ backgroundColor: 'rgba(154, 134, 253, 0.15)' }}>
41+
<p className="text-lg flex-1 text-center text-[#876fff84] flex items-center justify-center">Area 2</p>
42+
<Toaster
43+
toasterId="area2"
44+
position="top-center"
45+
containerStyle={{ position: 'absolute' }}
46+
toastOptions={{
47+
className: '!text-white px-4 py-2 border !rounded-full',
48+
style: {
49+
backgroundColor: 'rgb(154, 134, 253)',
50+
borderColor: 'rgba(154, 134, 253, 0.3)'
51+
}
52+
}}
53+
/>
54+
<button
55+
onClick={() => toast('Notification for Area 2', { toasterId: 'area2' })}
56+
className="text-white px-4 py-2 rounded-lg bg-[#9a86fd] w-full"
57+
>
58+
Show Toast in Area 2
59+
</button>
60+
</div>
61+
</div>
62+
63+
## Basic Usage
64+
65+
You can create multiple toasters providing unique `toasterId` to each `<Toaster />` component:
66+
67+
```jsx
68+
// Create a toaster with a unique id
69+
<Toaster toasterId="area1" />
70+
71+
// Create another toaster with a unique id
72+
<Toaster toasterId="area2" toastOptions={{ ... }} />
73+
```
74+
75+
To create a toast in a specific toaster, you can pass the `toasterId` to the `toast` function.
76+
77+
```jsx
78+
// Create a toast in area 1
79+
toast('Notification for Area 1', {
80+
toasterId: 'area1',
81+
});
82+
```
83+
84+
When no `toasterId` is provided, it uses `"default"` as the `toasterId`.
85+
86+
### Positioning the toaster
87+
88+
When placing a toaster in a specific area of your app, set the position to `absolute` and the parent element to `relative`.
89+
90+
```jsx
91+
<div style={{ position: 'relative' }}>
92+
<Toaster
93+
toasterId="area1"
94+
position="top-center"
95+
containerStyle={{ position: 'absolute' }}
96+
/>
97+
</div>
98+
```

site/pages/docs/toast.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ toast('Hello World', {
3939

4040
// Additional Configuration
4141
removeDelay: 1000,
42+
43+
// Toaster instance
44+
toasterId: 'default',
4245
});
4346
```
4447

site/pages/docs/toaster.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This component will render all toasts. Alternatively you can create own renderer
2020
gutter={8}
2121
containerClassName=""
2222
containerStyle={{}}
23+
toasterId="default"
2324
toastOptions={{
2425
// Define default options
2526
className: '',
@@ -67,6 +68,10 @@ Customize the style of toaster div. This can be used to change the offset of all
6768

6869
Changes the gap between each toast. Defaults to `8`.
6970

71+
### `toasterId` Prop
72+
73+
You can change the toasterId to have a different toaster instance. Learn more about [multiple toasters](/docs/multi-toaster). Defaults to `"default"`.
74+
7075
### `toastOptions` Prop
7176

7277
These will act as default options for all toasts. See [`toast()`](/docs/toast) for all available options.

site/pages/docs/use-toaster.mdx

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,79 @@ export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
99

1010
# `useToaster()` API
1111

12-
The `useToaster()` hook provides you a **headless system that will manage the notification state** for you. This makes building your own notification system much easier.
12+
The `useToaster()` hook provides a **headless toast management system** for building custom notification UIs. It manages toast state and lifecycle without rendering any components.
1313

14-
It solves the following problems for you:
14+
It handles pausing on hover, auto-removal, and provides a 1-second removal delay with `visible` flag for smooth animations.
1515

16-
- Built-in dispatch system with [`toast()`](/docs/toast)
17-
- Handlers to pause toasts on hover
18-
- Automatically remove expired toasts
19-
- Support for unmount animations. Removal is delayed by 1s, but sets `visible` on the toast to `false`.
16+
**Alternative**: Use [`useToasterStore()`](/docs/use-toaster-store) if you already have a toaster instance and only need the state.
2017

21-
### Importing from headless
18+
### Importing
2219

23-
You can import only the core of the library with `react-hot-toast/headless`. It won't include any styles, dependencies or custom components.
20+
```jsx
21+
import { useToaster } from 'react-hot-toast';
22+
```
23+
24+
You can also import from the headless entry point to exclude UI components:
2425

2526
```jsx
2627
import { useToaster } from 'react-hot-toast/headless';
2728
```
2829

29-
Be aware: [react-hot-toast 2.0](/docs/version-2) adds support for **custom render functions**, an easier method to render custom notification components.
30+
**Note**: [React Hot Toast 2.0](/docs/version-2) includes **custom render functions** for easier custom components.
3031

31-
It's recommended to only have one `<Toaster/>` or `useToaster()` in your app at a time. If you need the current state without the handlers, you should use [`useToasterStore()`](/docs/use-toaster-store) instead.
32+
## API Reference
3233

33-
## Usage with React Native
34+
### Parameters
35+
36+
```tsx
37+
useToaster(
38+
toastOptions?: DefaultToastOptions,
39+
toasterId?: string
40+
)
41+
```
42+
43+
| Parameter | Type | Default | Description |
44+
| -------------- | --------------------- | ----------- | ----------------------------------------------- |
45+
| `toastOptions` | `DefaultToastOptions` | `undefined` | Default options for all toasts in this instance |
46+
| `toasterId` | `string` | `'default'` | Unique identifier for this toaster instance |
47+
48+
### Returns
49+
50+
```tsx
51+
{
52+
toasts: Toast[];
53+
handlers: {
54+
startPause: () => void;
55+
endPause: () => void;
56+
updateHeight: (toastId: string, height: number) => void;
57+
calculateOffset: (toast: Toast, options?: OffsetOptions) => number;
58+
};
59+
}
60+
```
61+
62+
#### `toasts`
63+
64+
Array of all toasts in this toaster instance, including hidden ones for animation purposes.
65+
66+
#### `handlers`
3467

35-
Headless mode is perfectly suited to add notifications to your React Native app. You can check out [this example](<https://snack.expo.io/@timo/react-hot-toast---usetoaster()---react-native>).
68+
- **`startPause()`**: Pause all toast timers (useful for hover states)
69+
- **`endPause()`**: Resume all toast timers
70+
- **`updateHeight(toastId, height)`**: Update toast height for offset calculations
71+
- **`calculateOffset(toast, options)`**: Calculate vertical offset for toast positioning
72+
73+
## Multiple Toasters
74+
75+
You can create multiple independent toaster instances by providing a unique `toasterId`. See the [Multiple Toasters](/docs/multi-toaster) guide for detailed examples.
76+
77+
```jsx
78+
const sidebar = useToaster({ duration: 5000 }, 'sidebar');
79+
toast('Sidebar notification', { toasterId: 'sidebar' });
80+
```
3681

3782
## Examples
3883

39-
### Basic Example
84+
### Basic Implementation
4085

4186
```jsx
4287
import toast, { useToaster } from 'react-hot-toast/headless';
@@ -58,20 +103,20 @@ const Notifications = () => {
58103
);
59104
};
60105

61-
// Create toasts anywhere
106+
// Create toasts from anywhere
62107
toast('Hello World');
63108
```
64109

65-
### Animated Example
110+
### Animated Implementation
66111

67-
Instead of mapping over `visibleToasts` we'll use `toasts`, which includes all hidden toasts. We animate them based on `toast.visible`. Toasts will be removed from 1 second after being dismissed, which give us enough time to animate.
112+
This example uses all `toasts` (including hidden ones) to enable smooth animations. The `toast.visible` property controls opacity, while the 1-second removal delay provides time for exit animations.
68113

69-
You can play with the demo on [CodeSandbox](https://codesandbox.io/s/react-hot-toast-usetoaster-headless-example-zw7op?file=/src/App.js).
114+
**Live Demo**: [CodeSandbox](https://codesandbox.io/s/react-hot-toast-usetoaster-headless-example-zw7op?file=/src/App.js)
70115

71116
```jsx
72117
import { useToaster } from 'react-hot-toast/headless';
73118

74-
const Notifications = () => {
119+
const AnimatedNotifications = () => {
75120
const { toasts, handlers } = useToaster();
76121
const { startPause, endPause, calculateOffset, updateHeight } = handlers;
77122

@@ -92,11 +137,12 @@ const Notifications = () => {
92137
});
93138

94139
const ref = (el) => {
95-
if (el && typeof toast.height !== "number") {
140+
if (el && typeof toast.height !== 'number') {
96141
const height = el.getBoundingClientRect().height;
97142
updateHeight(toast.id, height);
98143
}
99144
};
145+
100146
return (
101147
<div
102148
key={toast.id}
@@ -119,3 +165,8 @@ const Notifications = () => {
119165
);
120166
};
121167
```
168+
169+
170+
## Usage with React Native
171+
172+
The headless API works perfectly with React Native. View the [React Native example](<https://snack.expo.io/@timo/react-hot-toast---usetoaster()---react-native>) for implementation details.

src/components/toaster.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,15 @@ export const Toaster: React.FC<ToasterProps> = ({
8888
toastOptions,
8989
gutter,
9090
children,
91+
toasterId,
9192
containerStyle,
9293
containerClassName,
9394
}) => {
94-
const { toasts, handlers } = useToaster(toastOptions);
95+
const { toasts, handlers } = useToaster(toastOptions, toasterId);
9596

9697
return (
9798
<div
98-
id="_rht_toaster"
99+
data-rht-toaster={toasterId || ''}
99100
style={{
100101
position: 'fixed',
101102
zIndex: 9999,

0 commit comments

Comments
 (0)