The easiest way to handle notifications in React Native Expo apps.
Bell provides simple, powerful React hooks for handling notification permissions, push tokens, badge counts, and notification responses with zero configuration. Managing Expo notifications can be complex and error-prone. Bell handles token refresh, permissions, and badge management for you - with type safety!
npm install expo-bellPeer Dependencies (install these if not already in your project):
expo install expo-notifications expo-device expo-constantsBefore using Bell, ensure your Expo app is configured for push notifications:
{
"expo": {
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#ffffff",
"sounds": ["./assets/notification-sound.wav"]
}
]
]
}
}import { useNotificationPermissions } from 'expo-bell';
function PermissionsExample() {
const { permission, loading, requestPermissions, checkPermissions } = useNotificationPermissions();
const handleRequestPermissions = async () => {
try {
const result = await requestPermissions();
if (!result.granted) {
Alert.alert('Permission Denied', 'Notifications are required for the best experience.');
}
} catch (error) {
Alert.alert('Error', 'Failed to request notification permissions');
}
};
return (
<View>
<Button
onPress={handleRequestPermissions}
title={loading ? "Requesting..." : "Request Notifications"}
disabled={loading}
/>
<Button
onPress={checkPermissions}
title="Check Permissions"
/>
{!permission?.granted ?
<Text>Notifications are not enabled.</Text>
:
<Text>Notifications are enabled! π</Text>
}
</View>
);
}import { useExpoPushToken } from 'expo-bell';
function PushTokenExample() {
const { token, loading, registerForPushTokens } = useExpoPushToken();
const handleRegister = async () => {
try {
const pushToken = await registerForPushTokens();
if (pushToken) {
// Send token to your backend server.
console.log('Expo Push Token:', pushToken);
}
} catch (error) {
Alert.alert('Error', 'Failed to register for push notifications');
}
};
return (
<View>
<Button
onPress={handleRegister}
title="Register for notifications and get token"
disabled={loading}
/>
{token && <Text>Expo Push Token: {token}</Text> }
</View>
);
}import { NotificationResponse, NotificationContent } from 'expo-notifications'
import { useNotificationResponse } from 'expo-bell';
function NotificationHandlerExample() {
const [lastNotification, setLastNotification] = useState<NotificationContent | null>(null);
const handleNotification = useCallback((response: NotificationResponse) => {
const { notification } = response;
setLastNotification(notification.request.content);
// Handle notification tap.
console.log('Notification tapped:', notification.request.content);
}, []);
useNotificationResponse(handleNotification);
return (
<View>
{lastNotification ? (
<View>
<Text>π§ Last Notification:</Text>
<Text>Title: {lastNotification.title}</Text>
<Text>Body: {lastNotification.body}</Text>
{lastNotification.data && (
<Text>Data: {JSON.stringify(lastNotification.data)}</Text>
)}
</View>
) : (
<Text>No notification has come yet.</Text>
)}
</View>
);
}Note:
useNotificationBadgeautomatically fetches the current badge count when the component mounts.
import { useNotificationBadge } from 'expo-bell';
function BadgeExample() {
const { badgeCount,
loading,
incrementBadgeCount,
decrementBadgeCount,
setBadgeCount,
clearBadgeCount
} = useNotificationBadge();
return (
<View>
<Text>Current Badge Count: {badgeCount}</Text>
<Button
onPress={incrementBadgeCount}
title="Increment Badge"
disabled={loading}
/>
<Button
onPress={decrementBadgeCount}
title="Decrement Badge"
disabled={loading}
/>
<Button
onPress={() => setBadgeCount(10)}
title="Set Badge to 10"
disabled={loading}
/>
<Button
onPress={clearBadgeCount}
title="Clear Badge"
disabled={loading}
/>
</View>
);
}- β Minimal Configuration - Works with standard Expo setup
- β Badge Management - Simple badge count utilities with automatic refresh
- β Permission Handling - Streamlined permission flow with error handling
- β Type Safety - Full TypeScript support
- β Custom Handlers - Hook into notification events and responses
- β Device Detection - Automatically handles emulator vs device
- β Loading States - Built-in loading indicators for all async operations
- β Error Handling - Comprehensive error handling with detailed logging
Manages notification permission state and requests.
Returns:
permission: PermissionResponse | null- Current permission statusloading: boolean- Loading state during permission operationsrequestPermissions: () => Promise<PermissionResponse>- Request notification permissionscheckPermissions: () => Promise<PermissionResponse>- Check current permissions
Handles Expo push token registration and management.
Returns:
token: string | null- The Expo push tokenloading: boolean- Loading state during token operationsregisterForPushTokens: () => Promise<string | null>- Register and get push token
Manages app badge count with automatic state synchronization.
Returns:
badgeCount: number- Current badge countloading: boolean- Loading state during badge operationssetBadgeCount: (count: number) => Promise<void>- Set badge to specific numberincrementBadgeCount: () => Promise<void>- Increment badge by 1decrementBadgeCount: () => Promise<void>- Decrement badge by 1 (minimum 0)clearBadgeCount: () => Promise<void>- Set badge to 0refreshBadgeCount: () => Promise<void>- Refresh badge count from system
Listens for notification responses (when user taps notifications).
Parameters:
handler: (response: NotificationResponse) => void | Promise<void>- Callback function
MIT Β© Gabriele Scotto di Vettimo
Issues and pull requests are welcome! Check out the GitHub repository.