Building a Scalable Global Toast System in React Native (With Clean Architecture)

I am developer from India.
When building mobile apps, one pattern shows up everywhere: ephemeral feedback — success messages, errors, warnings, etc.
A well-designed Toast system solves this cleanly.
In this article, we’ll:
Build a global toast system
Support multiple concurrent toasts
Add auto-dismiss + manual close
Keep the implementation clean, scalable, and interview-ready
🚨 The Problem With Naive Implementations
Most developers start with:
useStateboolean (isVisible)One toast at a time
Manual timers in components
This breaks down quickly:
❌ No support for multiple toasts
❌ Logic duplicated across screens
❌ Hard to control globally
✅ What We Want (Design Goals)
A proper system should:
Be globally accessible
Support stacking (multiple toasts)
Handle auto-dismiss
Allow manual close
Be decoupled from UI logic
🧠 Architecture Overview
We’ll use:
Context API→ global accessuseState→ reactive UI updatesUnique
id→ track each toastsetTimeout→ lifecycle management
🏗️ Step 1: Create Toast Context
import { createContext, useContext } from "react";
const ToastContext = createContext(null);
export const useToast = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error("useToast must be used within ToastProvider");
}
return context;
};
🏗️ Step 2: Build Toast Provider
This is the core engine.
import React, { useState } from "react";
import { View, Text, Pressable } from "react-native";
let idCounter = 0;
export const ToastProvider = ({ children }) => {
const [toasts, setToasts] = useState([]);
const showToast = ({
content,
duration = 3000,
backgroundColor = "#333",
}) => {
const id = idCounter++;
const newToast = { id, content, duration, backgroundColor };
// Add toast
setToasts((prev) => [...prev, newToast]);
// Auto remove
setTimeout(() => {
removeToast(id);
}, duration);
};
const removeToast = (id) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
};
return (
<ToastContext.Provider value={{ showToast }}>
{children}
{/* Toast Container */}
<View
style={{
position: "absolute",
bottom: 50,
left: 20,
right: 20,
}}
>
{toasts.map((toast) => (
<View
key={toast.id}
style={{
backgroundColor: toast.backgroundColor,
padding: 12,
borderRadius: 8,
marginBottom: 10,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Text style={{ color: "white" }}>{toast.content}</Text>
<Pressable onPress={() => removeToast(toast.id)}>
<Text style={{ color: "white" }}>✕</Text>
</Pressable>
</View>
))}
</View>
</ToastContext.Provider>
);
};
🏗️ Step 3: Use It Anywhere
const HomeScreen = () => {
const { showToast } = useToast();
return (
<Pressable
onPress={() =>
showToast({
content: "Toast triggered 🚀",
backgroundColor: "green",
duration: 2000,
})
}
>
<Text>Show Toast</Text>
</Pressable>
);
};
🏗️ Step 4: Wrap Your App
export default function App() {
return (
<ToastProvider>
<HomeScreen />
</ToastProvider>
);
}
⚙️ Why This Implementation Is Better
1. State-driven UI
We use:
const [toasts, setToasts] = useState([]);
This ensures UI re-renders automatically.
2. Immutable Updates
setToasts(prev => [...prev, newToast]);
No mutation → predictable behavior.
3. Independent Lifecycle
Each toast:
Has its own
idHas its own
timer
This enables:
Multiple toasts
Independent removal
4. Global Access Pattern
const { showToast } = useToast();
No prop drilling. Clean API.
🚀 Optimizations (Senior-Level Improvements)
🔹 1. Prevent Memory Leaks
Store timers and clear them on unmount:
const timers = useRef({});
timers.current[id] = setTimeout(() => {
removeToast(id);
}, duration);
// cleanup
useEffect(() => {
return () => {
Object.values(timers.current).forEach(clearTimeout);
};
}, []);
🔹 2. Limit Max Toasts
const MAX_TOASTS = 3;
setToasts(prev => {
const updated = [...prev, newToast];
return updated.slice(-MAX_TOASTS);
});
🔹 3. Add Types (Success / Error / Info)
const TYPE_STYLES = {
success: "green",
error: "red",
info: "blue",
};
🔹 4. Add Position Support
position: "top" | "bottom"
🔹 5. Add Animations (Highly Recommended)
Use:
react-native-reanimatedor
LayoutAnimation
This is what makes your implementation stand out in interviews.
🧩 Common Mistakes to Avoid
❌ Using
useReffor UI state❌ Mutating arrays directly
❌ Single boolean toast
❌ Global timers instead of per-toast timers
❌ No unique IDs
🧠 Final Thought
A toast system seems simple — but it's a great test of:
state management
component architecture
UI decoupling
lifecycle handling
If you can build this cleanly, you’re already thinking like a senior engineer.

