Reapop: The Complete Guide to Building a React Redux Notification System
Every production React application eventually needs a reliable way to tell users what’s happening — API calls succeed, form submissions fail, background jobs complete. You can build that plumbing yourself, of course. But unless you genuinely enjoy wiring up custom reducers and CSS animations at midnight, there’s a better option: reapop, a lightweight yet highly configurable React Redux notification library that plugs cleanly into your existing Redux store and stays completely out of your way until you need it.
This guide covers everything from reapop installation to advanced reapop customization with Redux Toolkit, hooks, and custom middleware. Whether you’re evaluating it as your next React notification system or you’ve already committed and just need a working example, you’ll find it all here — no filler, no hand-waving.
What Is Reapop and Why Does It Belong in Your React Stack?
Reapop is a fully-featured React notification library built around the Redux pattern. Instead of maintaining hidden component state or relying on a React context singleton that breaks under lazy loading, reapop stores every notification — its content, type, position, duration, and dismissal state — inside your Redux store. That single architectural decision makes notifications predictable, testable, and trivially replayable via Redux DevTools.
The practical upside: your API layer, your Redux Thunks, your Redux Saga workflows, or any piece of code that has access to the Redux dispatch function can fire a notification without needing to import a component, call a ref, or negotiate with React’s rendering pipeline. The notification system becomes a first-class citizen of your application state rather than a bolted-on UI trick.
Compared to alternatives like react-toastify or notistack, reapop gives you more explicit control over the React notification state and plays nicer with large-scale Redux architectures where you want every side effect traceable in a time-travel debugger. The trade-off is a slightly steeper setup — but that’s exactly what this guide eliminates for you.
Reapop Installation: Getting the Package Into Your Project
Reapop installation is a single command. The library has peer dependencies on React (≥16.8 for hooks support) and Redux, both of which you almost certainly already have. If you’re on a fresh project, initialize Redux first, then run:
# npm
npm install reapop
# yarn
yarn add reapop
# pnpm
pnpm add reapop
Once the package lands in node_modules, you’ll need to install a theme. Reapop deliberately ships without bundled styles to keep its core tiny. The community maintains several polished themes. The most widely used is reapop-theme-bootstrap, but there’s also a Semantic UI theme and a plain “wybo” theme if you prefer to start from zero:
npm install reapop-theme-bootstrap
That’s the full dependency footprint. No peer-dependency maze, no build plugin, no PostCSS config change required. The library is framework-agnostic in its Redux layer, meaning it works identically whether your project uses Create React App, Vite, Next.js (with client-side Redux), or a custom Webpack setup. The installation step is genuinely the easiest part of the whole process.
Reapop Setup: Wiring the Reducer and Provider Into Your App
The reapop setup has three moving parts: the Redux reducer, the NotificationsSystem UI component, and — if you’re using the hooks API — the provider wrapper. Get all three right and the system just works. Get one wrong and you’ll spend twenty minutes staring at a blank screen wondering why your dispatch calls disappear silently.
Start with the Redux store. Import reducer as notificationsReducer from reapop and register it under the notifications key. This key matters: the NotificationsSystem component reads from exactly that slice by default. If you’re using plain Redux:
// store.js (plain Redux)
import { createStore, combineReducers } from 'redux';
import { reducer as notificationsReducer } from 'reapop';
const rootReducer = combineReducers({
notifications: notificationsReducer,
// ...your other reducers
});
export const store = createStore(rootReducer);
If you’re using Redux Toolkit — which you probably should be in 2025 — the integration is equally clean:
// store.js (Redux Toolkit)
import { configureStore } from '@reduxjs/toolkit';
import { reducer as notificationsReducer } from 'reapop';
export const store = configureStore({
reducer: {
notifications: notificationsReducer,
// ...your slices
},
});
Next, drop the NotificationsSystem component somewhere high in your component tree — typically in App.jsx or your root layout. Pass it the theme object you installed earlier. The component renders nothing visible until a notification is dispatched, so placement is about access rather than visual hierarchy:
// App.jsx
import React from 'react';
import { Provider } from 'react-redux';
import NotificationsSystem, {
BootstrapTheme,
useNotifications,
} from 'reapop';
import 'reapop-theme-bootstrap/dist/reapop-theme-bootstrap.css';
import { store } from './store';
function App() {
const { notifications, dismissNotification } = useNotifications();
return (
<>
<NotificationsSystem
notifications={notifications}
dismissNotification={dismissNotification}
theme={BootstrapTheme}
/>
{/* rest of your app */}
</>
);
}
export default function Root() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
Note that useNotifications must be called inside a component that has access to the Redux store — which is why we split Root and App in the example above. This is the single most common gotcha in reapop getting started scenarios, and it bites almost everyone at least once.
Dispatching Notifications: The Core API Explained
Once the setup is complete, firing a React Redux toast or alert is a one-liner. Reapop exposes a notify action creator that accepts a message string and an optional configuration object. The configuration covers type (success, error, warning, info), position (nine grid positions from top-left to bottom-right), dismissibility, and duration in milliseconds.
// Inside any React component using hooks
import { useDispatch } from 'react-redux';
import { notify } from 'reapop';
function SaveButton() {
const dispatch = useDispatch();
const handleSave = async () => {
try {
await saveData();
dispatch(notify('Changes saved successfully!', 'success', { dismissAfter: 3000 }));
} catch (err) {
dispatch(notify('Save failed. Please try again.', 'error', { dismissible: true }));
}
};
return <button onClick={handleSave}>Save</button>;
}
The beauty of the Redux approach becomes obvious the moment you need to trigger a notification from outside a component — say, from a Redux Thunk that handles authentication, or from a Saga watching for API responses. Any code that can call store.dispatch or receives dispatch as an argument can fire a notification without importing a component or breaking the one-way data flow:
// authThunk.js
import { notify } from 'reapop';
export const loginUser = (credentials) => async (dispatch) => {
try {
const user = await authAPI.login(credentials);
dispatch({ type: 'auth/loginSuccess', payload: user });
dispatch(notify(`Welcome back, ${user.name}!`, 'success', { dismissAfter: 4000 }));
} catch {
dispatch(notify('Invalid credentials. Please try again.', 'error', { dismissible: true }));
}
};
This pattern — dispatching React Redux alerts directly from async action creators — is what separates reapop from context-based notification libraries that require a hook call inside a component. Your business logic layer stays clean; the UI layer renders whatever the store says to render.
Reapop Middleware: Intercepting and Enriching Notifications
One of reapop’s less-documented features is its reapop middleware — a standard Redux middleware you can optionally add to your store to intercept notification actions before they reach the reducer. This is useful when you want to apply global defaults (every error notification should be persistent, every success should auto-dismiss in three seconds) without passing those options every time you call notify.
// store.js with middleware
import { configureStore } from '@reduxjs/toolkit';
import { reducer as notificationsReducer, middleware as notificationsMiddleware } from 'reapop';
export const store = configureStore({
reducer: {
notifications: notificationsReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
notificationsMiddleware({
// Global defaults
dismissAfter: 5000,
dismissible: true,
position: 'top-right',
})
),
});
With this middleware in place, every call to notify that doesn’t explicitly set position or dismissAfter will inherit the global values. The individual notification can still override them — middleware defaults are merged, not enforced. This keeps your dispatch call sites terse and lets you enforce UX consistency from one place rather than hunting down forty notify() calls every time the design team changes the dismissal duration.
Beyond default merging, the middleware is also your integration point for logging and analytics. Because every notification passes through it as a plain Redux action, you can inspect the payload and forward it to your error tracking service, your analytics pipeline, or your development console without touching component code. This makes React notification state observable at the infrastructure level — a subtle but meaningful advantage in large-scale applications.
Reapop Customization: Themes, Positioning, and Custom Components
Out of the box, reapop customization works on three levels: theme swapping, CSS variable overriding, and full component replacement. Most projects stop at level two; power users go to level three when they need pixel-perfect control or want the notifications to match a design system that no off-the-shelf theme covers.
Theme swapping is the fastest path to a polished look. Import the theme object from your installed theme package and pass it to the theme prop of NotificationsSystem. The theme object tells reapop which CSS classes to apply to each notification state — it’s just a plain JavaScript object mapping state names to class strings, which means you can fork it and modify individual mappings without ejecting from the theme entirely:
import { BootstrapTheme } from 'reapop';
const customTheme = {
...BootstrapTheme,
notification: {
...BootstrapTheme.notification,
className: {
...BootstrapTheme.notification.className,
wrapper: 'my-custom-notification-wrapper',
},
},
};
For teams using CSS Modules, Tailwind, or a CSS-in-JS solution, the custom component approach is cleaner. Reapop accepts a NotificationComponent prop on NotificationsSystem. Whatever you pass there receives the full notification object as a prop and is responsible for rendering it. You get full creative control while reapop handles the Redux plumbing, the list rendering, the dismissal callbacks, and the entrance/exit animations via CSS transitions on the container:
function MyNotification({ notification, onDismiss }) {
return (
<div className={`toast toast--${notification.status}`}>
<span>{notification.message}</span>
{notification.dismissible && (
<button onClick={() => onDismiss(notification.id)}>✕</button>
)}
</div>
);
}
// In App.jsx
<NotificationsSystem
notifications={notifications}
dismissNotification={dismissNotification}
theme={BootstrapTheme}
NotificationComponent={MyNotification}
/>
React Notification Hooks: The Modern API Surface
If you’re working in a fully hooks-based codebase, reapop’s React notification hooks API is the cleanest integration path. The useNotifications hook returns the current list of notifications from the store, a notify-wrapped dispatch function, and a dismissNotification callback — everything you need in one destructuring assignment, no connect() boilerplate required.
import { useNotifications } from 'reapop';
function Dashboard() {
const { notify } = useNotifications();
return (
<button
onClick={() => notify('Report generated!', 'success', { dismissAfter: 3000 })}
>
Generate Report
</button>
);
}
Under the hood, useNotifications is just a thin wrapper around useSelector and useDispatch — no magic, no hidden subscriptions. If you ever want to read the raw notification array for a custom rendering implementation (say, showing a badge count in a header icon), you can select directly from the store using the same selector reapop uses internally: state.notifications. There’s no private API to worry about.
The hooks API also composes cleanly with other custom hooks. You can build a useApiRequest hook that wraps any API call and automatically dispatches a success or error notification based on the response, keeping that pattern DRY across your entire application. This is the kind of abstraction that makes a React notification system feel native rather than grafted on — and it’s only possible because the notification state lives in Redux rather than in component-local state.
A Complete Reapop Example: Putting It All Together
Theory is fine; a working project is better. The following is a minimal but complete reapop example that demonstrates installation, Redux Toolkit setup, middleware, hooks-based dispatch, and the Bootstrap theme — everything covered in this guide assembled into a copy-paste-ready structure.
The file structure you need:
src/store.js— Redux store with reapop reducer and middlewaresrc/App.jsx— Root component with NotificationsSystemsrc/features/UserForm.jsx— A form that dispatches notificationssrc/index.jsx— Entry point with Provider
// src/store.js
import { configureStore } from '@reduxjs/toolkit';
import {
reducer as notificationsReducer,
middleware as notificationsMiddleware,
} from 'reapop';
export const store = configureStore({
reducer: {
notifications: notificationsReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
notificationsMiddleware({ dismissAfter: 4000, position: 'top-right' })
),
});
// src/App.jsx
import React from 'react';
import NotificationsSystem, { BootstrapTheme, useNotifications } from 'reapop';
import 'reapop-theme-bootstrap/dist/reapop-theme-bootstrap.css';
import UserForm from './features/UserForm';
export default function App() {
const { notifications, dismissNotification } = useNotifications();
return (
<>
<NotificationsSystem
notifications={notifications}
dismissNotification={dismissNotification}
theme={BootstrapTheme}
/>
<main style={{ padding: '2rem' }}>
<h1>Reapop Demo</h1>
<UserForm />
</main>
</>
);
}
// src/features/UserForm.jsx
import React, { useState } from 'react';
import { useNotifications } from 'reapop';
export default function UserForm() {
const [name, setName] = useState('');
const { notify } = useNotifications();
const handleSubmit = (e) => {
e.preventDefault();
if (!name.trim()) {
notify('Name cannot be empty.', 'error');
return;
}
notify(`Hello, ${name}! Form submitted successfully.`, 'success');
setName('');
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<button type="submit">Submit</button>
</form>
);
}
// src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
Run your dev server and submit the form with an empty field — you’ll see an error notification slide in from the top right. Submit with a name and you get a success toast. Change the middleware defaults in store.js and every notification in the entire application updates instantly. That’s the payoff of treating React Redux notifications as first-class application state.
Advanced Patterns: Reapop in Real Production Environments
The advanced reapop + Redux patterns that appear in production codebases tend to cluster around two scenarios: centralized error handling and notification deduplication. Both are cleaner with reapop than with any component-state-based alternative.
Centralized error handling means that instead of wrapping every try/catch with a notify call, you write a single Redux middleware that watches for actions matching a naming convention (e.g., actions ending in /rejected when using Redux Toolkit’s createAsyncThunk) and dispatches an error notification automatically. Your feature code stays clean; the notification logic lives in one place and applies globally:
// errorNotificationMiddleware.js
import { notify } from 'reapop';
export const errorNotificationMiddleware = (store) => (next) => (action) => {
if (action.type?.endsWith('/rejected') && action.error) {
store.dispatch(
notify(action.error.message || 'An unexpected error occurred.', 'error', {
dismissible: true,
})
);
}
return next(action);
};
Notification deduplication solves the UX problem of rapid-fire actions (think autosave every keystroke) flooding the notification tray with identical success messages. Because all notifications live in the Redux store, you can inspect state.notifications in your middleware before dispatching and skip the action if an identical message is already visible. This kind of stateful awareness is simply not possible when notifications are managed inside a component that has no visibility into other components’ state.
For teams running React Redux alerts in micro-frontend architectures, reapop’s store-driven model means all micro-frontends can share a single notification tray by connecting to the same Redux store instance — or, if stores are isolated, by communicating via custom events that trigger store.dispatch(notify(...)) in the host shell. The pattern is flexible enough to handle both monolithic and distributed front-end setups without architectural gymnastics.
Frequently Asked Questions
How do I install and set up reapop in a React Redux project?
Run npm install reapop plus a theme package such as reapop-theme-bootstrap. Add the reapop reducer to your Redux store under the key notifications. Optionally add the reapop middleware for global defaults. Render the NotificationsSystem component near the root of your app, passing it the theme and the values returned by the useNotifications hook. You can then call dispatch(notify('message', 'success')) from any component or async action creator.
Does reapop work with Redux Toolkit and modern React hooks?
Yes, fully. Register the reapop reducer inside configureStore‘s reducer map and append the reapop middleware via getDefaultMiddleware().concat(notificationsMiddleware()). The built-in useNotifications hook works with any hooks-compatible React version (16.8+). Reapop also integrates naturally with createAsyncThunk — you can dispatch notifications from fulfilled and rejected cases or intercept them in a custom middleware.
Can I customize reapop notification styles and themes?
Yes, on multiple levels. The easiest is overriding CSS variables or class names in your global stylesheet after importing the theme CSS. The next level is spreading the theme object and replacing individual class name strings with your own. For full control, pass a custom NotificationComponent prop to NotificationsSystem — reapop will render your component for each notification, passing the full notification object and an onDismiss callback, while continuing to manage the Redux state, list rendering, and position layout.
For deeper dives, see the official reapop GitHub repository and the community walkthrough on advanced notification patterns with reapop and Redux.
Leave A Comment