react-toastsnack
Version:
An unopinionated notification queue for React
275 lines (212 loc) • 6.81 kB
Markdown
An unopinionated notification queue for React
Using [npm](https://www.npmjs.com/):
$ npm install react-toastsnack
Using [yarn](https://yarnpkg.com/):
$ yarn add react-toastsnack
Then with a module bundler like [webpack](https://webpack.github.io/), use as you would anything else:
```js
// Using ES6 Modules
import { useToastSnack } from 'react-toastsnack';
// using CommonJS modules
const useToastSnack = require('react-toastsnack').useToastSnack;
```
At the top-level of your application, import the `ReactToastSnackProvider` module.
```jsx
// AppContext.js
import React from 'react';
import { ReactToastSnackProvider } from 'react-toastsnack';
function AppContext() {
return (
<ReactToastSnackProvider>
<App />
</ReactToastSnackProvider>
);
}
```
The provider _provides_ (ahem) you with a set of parameters:
- [`renderer`](
- [`methods`](
- [`dismiss`](
- [`initial`](
- [`height`](
- [`offset`](
- [`delay`](
- [`max`](
This prop is required, and is the component used to render a single notification.
`react-toastsnack` has no opinion on how you choose to render it.
`Props` provided to the renderer:
- `toastSnack`
_ `id`
_ `open`
_ `offset`
_ And any other props passed upon creation of the notification.
- `onUpdate`
- `onExited`
- `onClose`
For example, let's say we're using the [material-ui component library](https://material-ui.com/).
The `renderer` may look something like this:
```jsx
// AppContext.js
import React from 'react';
import { ReactToastSnackProvider } from "react-toastsnack";
import Snackbar from "@material-ui/core/Snackbar";
function AppContext() {
return (
<ReactToastSnackProvider renderer={ToastSnack}>
<App />
</ReactToastSnackProvider>
)
}
const ToastSnack = React.memo(props => {
const { onUpdate, onExited, onClose, toastSnack } = props;
const {
id,
open,
offset
// Any other props are controlled by you.
style,
message,
contentProps,
autoHideDuration,
...other
} = toastSnack;
// If the snack height isn't the same as the default height
// which we provided to ReactToastSnackProvider, we may want to update it
const ref = useRef(null);
useLayoutEffect(() => {
const height = ref.current?.clientHeight;
if (height && snackProps.height !== height) {
onUpdate({ id, height });
}
}, [id, onUpdate, snackProps.height]);
const handleClose = React.useCallback((event,reason) => {
if (reason === "clickaway") return;
onClose(id);
}, [id, onClose]);
const handleExited = React.useCallback((event) => {
onExited(id);
}, [id, onExited]);
return (
<RootRef rootRef={ref}>
<Snackbar
style={style}
onClose={handleClose}
onExited={handleExited}
autoHideDuration={autoHideDuration}
{...other}
>
<SnackbarContent
message={message}
{...contentProps}
/>
</Snackbar>
</RootRef>
)
});
```
The `methods` prop allows you to pass an object containing functions, which generate custom methods.
Any functions you provide here, can be accessed through the `useToastSnack`/`withToastSnack` api.
For example, I want to implement a notification centered around a promise.
```jsx
// AppContext.js
import React from 'react';
import { ReactToastSnackProvider } from "react-toastsnack";
function AppContext() {
const methods = React.useMemo(() => {
// onCreate and onUpdate is passed, so we can create a custom flow.
promise: (onCreate, onUpdate) => {
return ({promise, ...other}) => {
// Create a new notification and save the id to a variable
// We've set the duration to null, in order to persist
// the notification until the promise is resolved or rejected
const id = onCreate({
...other,
duration: null,
color: "colorLoading",
message: "Please wait for something",
});
promise
// If the promise is resolved,
// change the color to `colorSuccess`, update the message
// and change the duration to 1000
.then(() => {
onUpdate({
...other,
id,
duration: 1000,
color: "colorSuccess",
message: "You bought 3 puppies!",
});
})
// If the promise is rejected, inform the user
// by changing the color to colorError and updating the message
// The duration is set to 5000
.catch(() => {
onUpdate({
id,
duration: 5000,
color: "colorError",
message: "An error occurred!",
})
})
}
}
})
return (
<ReactToastSnackProvider methods={methods} renderer={...}>
<App />
</ReactToastSnackProvider>
)
}
```
To use the custom `promise` method, just import `useToastSnack` and call it like any other internal method.
```jsx
// components/Elsewhere.js
import { useToastSnack } from 'react-toastsnack';
function Elsewhere() {
const toastSnack = useToastSnack();
// When the button is clicked, create a new promise
// to display a loading notification, which will change whenever the request responds
return (
<button
onClick={() => {
toastSnack.promise({
promise: fetch(puppies_url, { method: 'POST' }),
});
}}
>
Click here to buy puppies
</button>
);
}
```
Note that the `duration`, `color` and `message` variables in this example are completely arbitrary.
You have full control over any variables which are not `id`, `open` or `offset`.
`?boolean`: Whether or not we should dismiss active notifications to display new ones, if the `max` count has been reached.
Default: `false`
`?Array<ToastSnack>`: A list of initial notifications.
Default: `[]`
`?number`: The default assumed height for calculating offsets. This can be overwritten by using the `onUpdate` function.
Default: `48`
`?number`: The offset and distance between notifications.
Default: `8`
`?number`: The transition delay; How long to wait between a dismissal and queueing the next notification.
Default: `400`
`?number`: The maximum number of concurrent notifications.
Default: `null`
react-toastsnack is built and maintained by **babangsund**.
[@blog](https://babangsund.com/).
[@github](https://github.com/babangsund).
[@twitter](https://twitter.com/babangsund).