@magicbell/react-headless
Version:
Hooks to build a notification inbox
104 lines (88 loc) • 3.53 kB
text/typescript
import { useCallback, useEffect } from 'react';
import useConfig from '../stores/config/index.js';
import { useNotificationStoresCollection } from '../stores/notifications/index.js';
import { QueryParams } from '../types/INotificationsStoresCollection.js';
import INotificationStore from '../types/INotificationStore.js';
type FetchOptions = Partial<{
reset: boolean;
}>;
export interface NotificationStore extends INotificationStore {
isEmpty: boolean;
hasNextPage: boolean;
/**
* Fetch notifications from the API server. The pagination data is also
* updated. By default the array of notifications is not reset.
*
* @param queryParams Parameters to send to the API.
*/
fetch: (queryParams?: QueryParams, options?: FetchOptions) => Promise<void>;
/**
* Fetch the next page of notifications from the API server. The notifications
* will be appended to the current list of notifications.
*
* @param queryParams Parameters to send to the API.
*/
fetchNextPage: (queryParams?: Omit<QueryParams, 'page' | 'per_page'>, options?: FetchOptions) => Promise<void>;
/**
* Mark all notifications as seen. Resets the `unseenCount` attribute.
*
* @param options.persist Mark all notifications as seen in the server
* @param options.updateModels Mark all fetched notifications as seen
*/
markAllAsSeen: (options?: Partial<{ persist: boolean; updateModels: boolean }>) => Promise<boolean>;
/**
* Mark all notifications as read. Resets the `unreadCount` attribute.
*
* @param options.persist Mark all notifications as read in the server
* @param options.updateModels Mark all fetched notifications as read
*/
markAllAsRead: (options?: Partial<{ persist: boolean; updateModels: boolean }>) => Promise<boolean>;
}
/**
* Hook to get a notifications store from the stores collection. It fetches the
* first page of the store if it is not fetched already.
*
* @param storeId ID of the notifications store (optional)
*
* @example
* const store = useNotifications('mentions');
*/
export default function useNotifications(storeId = 'default'): NotificationStore | null {
const { stores, fetchStore, markAllAsSeen, markAllAsRead } = useNotificationStoresCollection();
const config = useConfig();
const store = stores[storeId];
const fetch = useCallback<NotificationStore['fetch']>(
(queryParams, options) => fetchStore(storeId, queryParams, options),
[fetchStore, storeId],
);
const fetchNextPage = useCallback<NotificationStore['fetchNextPage']>(
(queryParams = {}, options) => {
const page = store.currentPage + 1;
return fetchStore(storeId, { ...queryParams, page }, options);
},
[fetchStore, storeId, store?.currentPage],
);
useEffect(() => {
if (!store) return;
if (!config.lastFetchedAt || store.lastFetchedAt) return;
void fetch({ page: 1 });
}, [config.lastFetchedAt, store, fetch]);
const markAllAsReadFn = useCallback<NotificationStore['markAllAsRead']>(
(options) => markAllAsRead({ ...options, storeId }),
[markAllAsRead, storeId],
);
const markAllAsSeenFn = useCallback<NotificationStore['markAllAsSeen']>(
(options) => markAllAsSeen({ ...options, storeId }),
[markAllAsSeen, storeId],
);
if (!store) return null;
return {
...store,
isEmpty: store.notifications.length === 0,
hasNextPage: store.currentPage < store.totalPages,
fetch,
fetchNextPage,
markAllAsSeen: markAllAsSeenFn,
markAllAsRead: markAllAsReadFn,
};
}