Using Redux to Control Toast/Notification System in React — From Scratch

There’s something oddly satisfying about a toast notification popping up at just the right time — whether it’s a subtle “Saved successfully” or a sharp “Something went wrong,” these tiny messages quietly guide users through your app.

When I was building one of my React projects, I hit a point where I needed a global toast system — one that was fully in my control, could be triggered from anywhere, and wouldn’t become a dependency nightmare down the line.

Yes, there are fantastic libraries like react-toastify. But as a developer who likes to understand what’s under the hood, I decided to build my own — using Redux Toolkit as the brain behind it.

Why Redux?
Because when you need consistent state management, global access, and predictable behavior — Redux shines.
AI-generated illustration representing a React + Redux toast notification system

AI-generated image of a React + Redux toast notification system

In this tutorial, I’ll walk you through how I built a reusable toast/notification system using:

  • React + Vite
  • Redux Toolkit
  • Tailwind CSS

You’ll learn how to:

  • Set up Redux in a new project
  • Design a toast UI that adapts to different message types
  • Trigger toasts from anywhere in your app
  • Auto-dismiss them after a few seconds

By the end, you’ll have a working toast system that’s easily extendable for your own apps.


📚 Table of Contents

  1. Project Setup: A Clean Slate with Vite
  2. Suggested File Structure
  3. Setting Up Redux: Store & Toast Slice
  4. Creating the Toast UI with Tailwind CSS
  5. Triggering Toasts in Your App
  6. Run It!
  7. Conclusion: Why This Matters
  8. Next Steps

Project Setup: A Clean Slate with Vite

I always prefer starting fresh, especially when building reusable components like a toast system. So here’s how I spun up the project from scratch.

Step 1: Create the Project Folder

mkdir redux-toast-demo
cd redux-toast-demo

Step 2: Scaffold a React App with Vite

npm create vite@latest . -- --template react
npm install

Step 3: Install Redux Toolkit + React-Redux

npm install @reduxjs/toolkit react-redux

Suggested File Structure


redux-toast-demo/
├── src/
│   ├── app/
│   │   └── store.js
│   ├── features/
│   │   └── toast/
│   │       └── toastSlice.js
│   ├── components/
│   │   └── Toast.jsx
│   ├── App.jsx
│   └── main.jsx

Setting Up Redux: Store & Toast Slice

Step 1: Create the Redux Store

src/app/store.js

import { configureStore } from '@reduxjs/toolkit';
import toastReducer from '../features/toast/toastSlice';

export const store = configureStore({
  reducer: {
    toast: toastReducer,
  },
});

Step 2: Create the Toast Slice

src/features/toast/toastSlice.js

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  toasts: [],
};

let nextId = 0;

const toastSlice = createSlice({
  name: 'toast',
  initialState,
  reducers: {
    addToast: {
      reducer(state, action) {
        state.toasts.push(action.payload);
      },
      prepare(message, type = 'info') {
        return {
          payload: {
            id: nextId++,
            message,
            type,
          },
        };
      },
    },
    removeToast(state, action) {
      state.toasts = state.toasts.filter(t => t.id !== action.payload);
    },
  },
});

export const { addToast, removeToast } = toastSlice.actions;
export default toastSlice.reducer;

Step 3: Connect Redux to the App

src/main.jsx

import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './app/store';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Creating the Toast UI with Tailwind CSS

Toast Component

src/components/Toast.jsx

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { removeToast } from '../features/toast/toastSlice';

const Toast = () => {
  const dispatch = useDispatch();
  const toasts = useSelector((state) => state.toast.toasts);

  useEffect(() => {
    const timers = toasts.map((toast) =>
      setTimeout(() => dispatch(removeToast(toast.id)), 3000)
    );
    return () => timers.forEach(clearTimeout);
  }, [toasts, dispatch]);

  return (
    <div className="fixed top-5 right-5 space-y-2 z-50">
      {toasts.map((toast) => (
        <div
          key={toast.id}
          className={`px-4 py-2 rounded shadow-md text-white ${
            toast.type === 'success'
              ? 'bg-green-500'
              : toast.type === 'error'
              ? 'bg-red-500'
              : 'bg-blue-500'
          }`}
        >
          {toast.message}
        </div>
      ))}
    </div>
  );
};

export default Toast;

Triggering Toasts in Your App

App.jsx

src/App.jsx

import React from 'react';
import Toast from './components/Toast';
import { useDispatch } from 'react-redux';
import { addToast } from './features/toast/toastSlice';

function App() {
  const dispatch = useDispatch();

  const handleShowToast = (type) => {
    dispatch(addToast(`This is a ${type} message`, type));
  };

  return (
    <div className="min-h-screen flex flex-col items-center justify-center gap-4">
      <h1 className="text-2xl font-bold">Redux Toast Demo</h1>
      <div className="flex gap-2">
        <button
          className="bg-blue-500 text-white px-4 py-2 rounded"
          onClick={() => handleShowToast('info')}
        >
          Show Info
        </button>
        <button
          className="bg-green-500 text-white px-4 py-2 rounded"
          onClick={() => handleShowToast('success')}
        >
          Show Success
        </button>
        <button
          className="bg-red-500 text-white px-4 py-2 rounded"
          onClick={() => handleShowToast('error')}
        >
          Show Error
        </button>
      </div>

      <Toast />
    </div>
  );
}

export default App;

Run It!

npm run dev

Open http://localhost:5173 and click any of the buttons. You’ll see beautiful toast notifications pop in and auto-dismiss — exactly like you'd expect in a modern app.


Conclusion: Why This Matters

By combining Redux Toolkit with React and Tailwind CSS, we now have a:

  • Reusable, scalable toast notification system
  • Globally accessible solution with Redux
  • Auto-dismiss behavior without external libraries

If you’ve ever wanted more control over your app’s toast notifications, this is the perfect lightweight solution to start with.


Next Steps

  • Add animations with framer-motion
  • Add dismiss (✕) buttons to manually close toasts
  • Queue toasts to prevent overlap
  • Support position (top-left, bottom-right, etc.)

Want the full code? Check out the GitHub Repo

Comments