This package contains React components to build a notification inbox for your site powered by MagicBell. Feel free to explore using our Playground
Quick Start
shellnpm i @magicbell/magicbell-react
# or
yarn add @magicbell/magicbell-react
jsximport React from 'react';
import ReactDOM from 'react-dom';
import MagicBell, { FloatingNotificationInbox } from '@magicbell/magicbell-react';
ReactDOM.render(
{(props) => }
,
document.body,
);
MagicBell
The MagicBell
component is the default export of this package and is the root component for building a notification inbox. It initializes a connection to the MagicBell server, renders a bell icon with the number of unseen notifications and keeps the data updated in real time.
jsximport MagicBell, { FloatingNotificationInbox } from "@magicbell/magicbell-react";
function MyComponent({ user }) {
return (
{(props) => (
)}
);
}
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
apiKey | string | The API key of your magicbell.io project |
userEmail | string | The email of the user you want to show notifications for |
userExternalId | string | The external ID of the user you want to show notifications for. See the Identifying Users documentation for an example. |
userKey | string | The HMAC for the user. It is recommended to enable HMAC authentication but not required |
children | ({ isOpen, toggle, launcherRef }) => JSX.Element | The children function to render a list of notifications for the user |
theme | IMagicBellTheme | An optional object containing custom color values for the widget, see Custom Themes |
BellIcon | JSX.Element | An optional react element to be displayed instead of the default bell icon |
Badge | ({ count: number }) => JSX.Element | An optional custom component to use as notification badge |
defaultIsOpen | boolean | An optional flag to set the default visibility state of the element returned by the children function. It is false by default. |
onNewNotification | (notification) => void | An optional function called when a new notification arrives. |
onToggle | (isOpen) => void | An optional function called when the bell is clicked. |
isOpen | boolean | An optional flag to control the visibility state of the element returned by the children function. |
bellCounter | string | Counter to show in the bell. If set to 'unread' it will show the number of unread notifications. It is set to 'unseen' by default. |
Children function
This component expects a children function. This is how you render whatever you want to based on the state of the MagicBell
.
You can use the standard notification inbox:
javascriptimport React from 'react';
import ReactDOM from 'react-dom';
import MagicBell from '@magicbell/magicbell-react';
ReactDOM.render( , document.body);
Or the standard inbox with basic customizations (see NotificationInbox
):
javascriptimport React from 'react';
import ReactDOM from 'react-dom';
import MagicBell, { NotificationInbox } from '@magicbell/magicbell-react';
ReactDOM.render(
{() => }
,
document.body,
);
Or use your own:
javascriptimport React from 'react';
import ReactDOM from 'react-dom';
import MagicBell from '@magicbell/magicbell-react';
ReactDOM.render(
{({ launcherRef, isOpen, toggle }) => (
)}
,
document.body,
);
The MagicBell
component does not render the component returned by the children function by default, only the bell is rendered. When the bell is clicked, the child component is toggled. This behaviour can be changed using the defaultIsOpen
flag.
As shown above, the children function gets a function to manually toggle the notification inbox. You can access the notifications store through MagicBellContext
or using the useMagicBellContext
hook.
javascriptimport { useMagicBellContext } from '@magicbell/magicbell-react';
const { rootStore } = useMagicBellContext();
Tip: Initialize a MagicBell instance with a custom stores definition to render an inbox with two or more filtered lists:
javascriptimport React from 'react';
import ReactDOM from 'react-dom';
import MagicBell, { NotificationList } from '@magicbell/magicbell-react';
ReactDOM.render(
{(props) => (
<>
>
)}
,
document.body,
);
MagicBellProvider
The MagicBellProvider
component is the main component for building a custom notification inbox. It initializes a connection to magicbell.io, creates a MagicBellContext
and keeps the list of notifications updated in real time.
This is a headless component.
javascriptimport React from 'react';
import ReactDOM from 'react-dom';
import { MagicBellProvider } from '@magicbell/magicbell-react';
ReactDOM.render(
,
document.body,
);
MagicBellProvider
creates a React context object, so now you can access the MagicBellContext
anywhere in your application.
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
apiKey | string | The API key of your magicbell.io project |
userEmail | string | The email of the user you want to show notifications for |
userExternalId | string | The external ID of the user you want to show notifications for. See the Identifying Users documentation for an example. |
userKey | string | The HMAC for the user. It is recommended to enable HMAC authentication but not required |
children | JSX.Element | The children to be wrapped in a MagicBellContext.Provider |
theme | IMagicBellTheme | An optional object containing custom color values for the widget, see Custom Themes |
stores | object[] | An optional object containing the definitions of the notification stores to create. |
NotificationInbox
The NotificationInbox
component renders a header, a footer and an infinite scroll list of notifications.
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
height | number | Optional height (in pixels) of the infinite scroll list |
onAllRead | () => void | An optional callback function invoked when the "Mark All Read" button is clicked |
onNotificationClick | (notification) => boolean \| void | An optional callback function invoked when a notification is clicked. Return false when you wish to prevent opening the notification action url. |
storeId | string | ID of the store to render (optional) |
EmptyInboxPlaceholder | () => JSX.Element | An optional custom component to use as placeholder when there are no notifications |
NotificationItem | ({ notification, onItemClick }) => JSX.Element | An optional custom component to use for rendering each notification. Defaults to ClickableNotification. |
NotificationPreferences | () => JSX.Element | An optional custom component to use for rendering preferences. Shown when the user clicks the settings button` |
notificationPreferencesEnabled | boolean | Optional, and defaults to true . Set to false to hide the settings button. |
tabs | { storeId: string, label: string }[] | Optional, a map to render inbox tabs, where each tab is connected to a defined store. |
If the store wasn't fetched previously, this component will fetch the first page on first render.
Example: an inbox using tabs and custom stores:
javascriptconst stores = [
{ id: 'default', defaultQueryParams: {} },
{ id: 'unread', defaultQueryParams: { read: true } },
{ id: 'billing', defaultQueryParams: { categories: ['billing'] } },
];
// can list all stores, but doesn't need to
const tabs = [
{ storeId: 'default', label: 'Latest' },
{ storeId: 'unread', label: 'Archive' },
{ storeId: 'billing', label: 'Billing' },
];
;
FloatingNotificationInbox
This component renders a NotificationInbox
component in a tooltip. The tooltip is created with popper.
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
isOpen | boolean | Whether the notification inbox is open (visible) or not |
toggle | () => void | Optional function to toggle the notification inbox |
launcherRef | React.RefObject | React ref pointing to the element that toggles the notification inbox |
placement | string | Position of the notification inbox relative to the launcher. It can be one of these: "auto", "auto-start", "auto-end", "top", "bottom", "right", "left", "top-start", "top-end", "bottom-start", "bottom-end", "right-start", "right-end", "left-start", "left-end". |
width | number | Optional width (in pixels) of the list of notifications. Defaults to 500. |
height | number | Optional height (in pixels) of the list of notifications. Defaults to the window height. |
onAllRead | () => void | An optional callback function invoked when the "Mark All Read" button is clicked |
onNotificationClick | (notification) => boolean \| void | An optional callback function invoked when a notification is clicked. Return false when you wish to prevent opening the notification action url. |
closeOnClickOutside | boolean | Whether to close the inbox when the user clicks outside of it or not. Defaults to true. |
closeOnNotificationClick | boolean | Whether to close the inbox when the user clicks on a notification or not. Defaults to true. |
hideArrow | boolean | Whether to hide the pointing arrow or not. Defaults to false. |
NotificationItem | ({ notification, onItemClick }) => JSX.Element | An optional custom component to use for rendering each notification. Defaults to ClickableNotification. |
NotificationList
The NotificationList
component renders an infinite scroll list of notifications. When the user scrolls to the bottom the next page of notifications are fetched and appended to the current array of notifications. By default it renders a ClickableNotification
component for each item in the notifications store.
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
height | number | Height in pixels of the infinite scroll list |
onItemClick | (notification) => void | An optional callback function invoked when a notification is clicked |
ListItem | ({ notification, onItemClick }) => JSX.Element | An optional custom component to use for each item in the list |
notifications | NotificationStore | A store of notifications to render |
queryParams | object | An object with some query parameters to fetch the list. Do not include the page to fetch as this is handled by the component |
If the height property is not provided, then the window scroll will be used.
Example: notification inbox with a custom list item.
ClickableNotification
This component renders the title and content of a notification.
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
notification | Notification | The notification object |
onClick | (notification) => boolean \| void | An optional callback function invoked when the component is clicked. Return false when you wish to prevent opening the notification action url. |
IMPORTANT: When a notification is clicked, the notification is marked as read. If you implement your own component, you might also want to mark the notification as read manually. E.g.:
javascriptimport React from 'react';
import { useNotification } from '@magicbell/magicbell-react';
export default function CustomNotification({ notification: data, onClick }) {
const notification = useNotification(data);
const handleClick = () => {
notification.markAsRead();
onClick(notification);
};
return {notification.title};
}
NotificationContent
This component renders the notification's content, by injecting the notification content as HTML. As the notification content comes from a safe source - your server - we do not sanitize the content. If you need to sanitize content - because the notification contains user generated info - you'd need to do so when creating the notification on your backend.
These are all the properties accepted by this component.
Property | Type | Description |
---|---|---|
notification | Notification | The notification object |
TIP: If the content has a time
tag with the datetime
attribute, the content of this tag will be replaced with a relative time. For example, <time datetime="2021-06-10T03:00:00Z">on Jun 10</time>
will be replaced with <time>in 3 months</time>
.
NotificationPreferences
The preferences component enables you to create your own notification preferences page. By default it shows all notification channels and all categories, but it's possible to only show a subset of the available options.
Properties
Property | Type | Description |
---|---|---|
channels | string[] | An optional string array specifying channel slugs to include in the preferences grid. When channels is empty or absent, all channels are shown. |
categories | string[] | An optional string array specifying category slugs to include in the preferences grid. When categories is empty or absent, all categories are shown. |
onChange | ({ category: CategoryChannelPreference }) => void | An optional onChange handler, that is invoked after our server has responded to the change. |
Show preference for all channels & all categories
jsximport { MagicBellProvider, NotificationPreferences } from '@magicbell/magicbell-react';
function Component() {
return (
);
}
Show preference for specific channels, for specific categories
jsximport { MagicBellProvider, NotificationPreferences } from '@magicbell/magicbell-react';
function Component() {
return (
);
}
Invoke a callback when a preference gets updated
jsximport { MagicBellProvider, NotificationPreferences } from '@magicbell/magicbell-react';
function Component() {
return (
alert('preferences updated!')} />
);
}
useMagicBellEvent
This a hook to listen to realtime events.
javascriptimport { useMagicBellEvent } from '@magicbell/magicbell-react';
useMagicBellEvent('notifications.new', showPushNotification);
This is a list of events you can listen to:
Event name | Description |
---|---|
* | Any event |
notifications.new | A new notification for the authenticated user was created |
notifications.read | A notification was marked as read |
notifications.read.all | All notifications were marked as read |
notifications.unread | A notification was marked as unread |
notifications.seen.all | All notifications were marked as seen |
notifications.delete | A notification was deleted |
useNotifications
Hook to get notifications for the current authenticated user. Returns a NotificationStore
.
javascriptimport { useNotifications } from '@magicbell/magicbell-react';
function NotificationsList() {
const store = useNotifications();
return (
{store?.notifications.map((notification) => (
- {notification.title}
))}
);
}
You can optionally provide a name for the store you want to get, e.g.:
javascriptimport { useNotifications } from '@magicbell/magicbell-react';
const store = useNotifications('mentions');
Custom Themes
It is possible to customize the text color, font size and border radius of some elements by providing to the MagicBell
component a theme
property. This is going to be deep merged with the default theme. Note that the notification unread and unseen definitions inherit properties from notification default style.
This is the definition of the default theme:
javascript{
prose: {
headings: '#0f172a',
links: '#0f172a',
bold: '#0f172a',
hr: '#e2e8f0',
quotes: '#0f172a',
quoteBorders: '#e2e8f0',
captions: '#64748b',
code: '#0f172a',
preCode: '#e2e8f0',
preBg: '#1e293b',
thBorders: '#cbd5e1',
tdBorders: '#e2e8f0',
buttonBorders: '#1e293b',
buttons: '#0f172a',
fontMono: 'ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace',
},
icon: {
borderColor: '#3498F4',
width: '24px',
},
header: {
backgroundColor: '#3498F4',
backgroundOpacity: 1,
borderRadius: '8px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
fontSize: '14px',
fontWeight: 'inherit',
textColor: 'white',
textAlign: 'left',
textTransform: 'uppercase',
padding: '16px 24px',
borderColor: undefined,
},
footer: {
backgroundColor: '#3498F4',
backgroundOpacity: 1,
borderRadius: '8px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
fontSize: '14px',
fontWeight: 'inherit',
textColor: 'white',
textAlign: 'right',
textTransform: 'none',
padding: '16px 24px',
borderColor: undefined,
},
banner: {
backgroundColor: '#3498F4',
backgroundOpacity: 0.1,
textColor: '#000',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
textAlign: 'left',
fontSize: '14px',
boxShadow: 'none',
},
unseenBadge: {
backgroundColor: '#DF4759',
backgroundOpacity: 1,
borderRadius: '2px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
fontSize: '14px',
fontWeight: 'inherit',
textColor: 'white',
textAlign: 'left',
textTransform: 'none',
},
container: {
backgroundColor: '#FFFFFF',
backgroundOpacity: 1,
borderRadius: '8px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
fontWeight: 'inherit',
fontSize: '14px',
textAlign: 'left',
textColor: '#3A424D',
textTransform: 'none',
boxShadow: '0px 20px 25px rgba(84, 95, 111, 0.1), 0px 10px 10px rgba(84, 95, 111, 0.04)',
},
notification: {
default: {
backgroundColor: 'transparent',
backgroundOpacity: 0,
borderRadius: '8px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
fontSize: '14px',
fontWeight: 'inherit',
textColor: '#000',
textAlign: 'left',
textTransform: 'none',
margin: '4px',
padding: '16px 20px 16px 12px',
title: {
fontFamily: 'inherit',
fontSize: 'inherit',
fontWeight: 500,
textColor: 'inherit',
},
hover: {
backgroundColor: '#3498F4',
backgroundOpacity: 0.1,
},
state: {
color: 'transparent',
},
},
unread: {
backgroundColor: '#D9E2EF',
backgroundOpacity: 0.1,
state: {
color: '#3498F4',
},
},
unseen: {
backgroundColor: '#D9E2EF',
backgroundOpacity: 0.05,
state: {
color: '#3498F4',
},
},
},
}
You can override any attribute of this theme. Colors can be expressed in HEX, HSL, HSV or RGB(A).
javascriptimport React from 'react';
import ReactDOM from 'react-dom';
import MagicBell, { NotificationInbox } from '@magicbell/magicbell-react';
const customTheme = {
icon: {
borderColor: 'rgba(160, 30, 120, 0.5)',
},
header: {
borderRadius: '2px',
},
footer: {
borderRadius: '2px',
},
};
ReactDOM.render(
{() => }
,
document.body,
);
Custom translations
All elements in the inbox can be translated by providing a translations
object to the MagicBell
component. This includes category labels:
typescriptconst customLocale = {
name: 'en',
translations: {
header: {
title: 'Notifications',
},
preferences: {
categories: { // mapping from slug > label
billing: 'My Billing',
},
},
},
};
function MyComponent() {
return (
{(props) => }
);
}
The notification model
The Notification
class can be imported from @magicbell/magicbell-react
. It implements this interface.
typescriptinterface INotification {
// Attributes
id: string | null;
title: string;
content: string | null;
category: string | null;
actionUrl: string;
customAttributes: any;
readAt: number | null;
seenAt: number | null;
sentAt: number;
// Getters/setters
isRead: boolean;
isSeen: boolean;
// Read-only properties
seenAtDate: Dayjs | null;
sentAtDate: Dayjs;
readAtDate: Dayjs | null;
// Methods
fetch: () => Promise;
markAsRead: () => Promise;
markAsUnread: () => Promise;
delete: () => Promise;
}
seenAtDate
A date representation of the seenAt
attribute. It returns an immutable instance of Dayjs. Dayjs exposes an API similar to moment.js.
javascriptnotification.seenAtDate.format('DD/MM/YYYY'); // '01/04/2021'
notification.seenAtDate.fromNow(); // 1mo
notification.seenAtDate.to('2021-01-01'); // in 4mo
notification.seenAtDate.add(2, 'day');
readAtDate
A date representation of the readAt
attribute. It returns an immutable instance of Dayjs.
sentAtDate
A date representation of the sentAt
attribute. It returns an immutable instance of Dayjs.
fetch
Fetches the notification from the magicbell.io server. All fetched attributes are assigned to the current object.
markAsRead
This method makes a POST request to the read notification API endpoint of magicbell.io. It sets the readAt
attribute as well.
markAsUnread
This method makes a POST request to the unread notification API endpoint of magicbell.io. It sets the readAt
attribute to null
as well.
delete
This method makes a DELETE request to the notification API endpoint of magicbell.io. If the notification belongs to a store, it will remove itself from the store.
The notification store
The NotificationStore
class implements this interface:
typescriptinterface INotificationStore {
unseenCount: number;
unreadCount: number;
total: number;
perPage: number;
totalPages: number;
currentPage: number;
notifications: Notification[];
length: number;
isEmpty: boolean;
hasNextPage: boolean;
fetch: (queryParams, options = { reset: false }) => Promise;
fetchNextPage: (queryParams) => Promise;
markAllAsRead: () => Promise;
markAllAsSeen: () => Promise;
}
TIP: You can import this class from @magicbell/magicbell-react
. However, you may want to access he notifications store through the MagicBellContext
, instead of creating a new instance in your app.
fetch
Fetch notifications from the magicbell server. The pagination data is also updated. The provided query parameters are included in the request to the server.
The response is appended to the current array of notifications, so it can be used as the view model for an infinite scroll list. If you want to reset the collection instead, pass the reset: true
option to this method:
jsnotifications.fetch({ page: 2 }, { reset: true });
fetchNextPage
This method is simply wrapping the fetch
method, sending as a parameter the next page of notifications. You can include query parameters to this method.
create
Create a new notification.
It is equivalent to creating a Notification
instance with some attributes, saving the notification to the server, and adding it to the array of items
after being successfully created.
markAllAsRead
Makes a POST request to the read notifications API endpoint. It also marks all notifications in the collection as read.
markAllAsSeen
Makes a POST request to the seen notifications API endpoint. It also sets the unseenCount
to 0 and marks all notifications in the collection as seen.
remove
Removes the given notification from the items
array. It does not make any request to the server. If you want to delete a notification, use the delete
method of the notification object instead.