In modern React applications, showing success or error messages after a user action (like form submission or data saving) improves the user experience.
Instead of repeating the same notification code in every component, we can centralize it using React Context API — a powerful feature for managing global state without external libraries like Redux.

In this tutorial, we’ll build a simple but reusable notification system that displays messages (Success / Error) at the top of the page.
Project Setup
Let’s start by creating a React + TypeScript project.
npx create-vite@latest notifyapp cd notifyapp npm install @heroicons/react
We’ll use Tailwind CSS for styling (optional but recommended).
Follow Tailwind setup guide from:
https://tailwindcss.com/docs/guides/create-react-app

Step 1: Create Flash Context:
File: src/context/flash.tsx
import React from 'react';
type FlashProviderPropType = {
children: React.ReactNode;
};
const defaultMessage = {
type: "",
message: ""
}
type MessagePropType = {
type?: "info"|"success"|"error";
message: string|string[]
}|null;
interface Flash {
message: MessagePropType,
setFlash: React.Dispatch<React.SetStateAction<MessagePropType>>
}
const FlashContext = React.createContext<Flash>({} as Flash);
const FlashProvider: React.FC<FlashProviderPropType> = ({ children }) => {
const [message, setFlash] = React.useState(defaultMessage as MessagePropType);
return (
<FlashContext.Provider value={{ message, setFlash }}>
{children}
</FlashContext.Provider>
);
};
export { FlashContext, FlashProvider };
Explanation:
- We define a global
FlashContextto share the current notification message and a setter function. - The provider wraps our app so any component can show a flash message.
- The
messagehas two properties:type:"success" | "error"message: can be a string or an array of error messages.Step 2: Create the Notification Component
File:
src/components/flash.tsximport React from 'react' import { FlashContext } from "../context/flash"; import { CheckCircleIcon } from '@heroicons/react/20/solid' import { XCircleIcon } from '@heroicons/react/20/solid' const Flash = () => { const { message, setFlash } = React.useContext(FlashContext); React.useEffect( () => { const timer1 = setTimeout(() => setFlash(null), 4000); return () => { clearTimeout( timer1 ); } }, [setFlash, message] ) if( !message || !message?.message ) { return null; } if( !message?.type ) { message.type = "success" } const Success = () => { return ( <div className="rounded-md bg-green-50 p-2"> <div className="flex max-w-lg mx-auto items-center"> <div className="shrink-0"> <CheckCircleIcon aria-hidden="true" className="size-5 text-green-400" /> </div> <div className="text-left align-middle pl-1"> <h3 className="text-sm font-medium text-green-800">{message.message}</h3> </div> <div className="shrink-0 ml-auto"> <button type="button" className="ml-3 rounded-md px-2 py-0 text-xs font-normal text-green-800 bg-green-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" onClick={()=>setFlash(null)} > Dismiss </button> </div> </div> </div> ) } const Error = () => { let errors: string[] = []; // console.log("message.message typeof", typeof message.message) if(typeof message.message === 'object' && message.message.length > 0 ) { errors = message.message; } else if(typeof message.message === 'string') errors.push(message.message) return ( <div className="rounded-md bg-red-50 p-2"> <div className="flex items-start max-w-lg mx-auto"> <div className="shrink-0 items-start"> <XCircleIcon aria-hidden="true" className="size-5 text-red-400" /> </div> <div className="text-left align-middle pl-1"> <h3 className="text-sm font-medium text-red-800">There were {errors.length} errors with your submission</h3> <div className="mt-2 text-sm text-red-700"> <ul role="list" className="list-disc space-y-1 pl-5"> {errors.map((value, index) => <li key={`err-${index}`}>{value}</li>)} </ul> </div> </div> <div className="shrink-0 ml-auto"> <button type="button" className="ml-3 rounded-md px-2 py-0 text-xs font-normal text-red-800 bg-red-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600" onClick={()=>setFlash(null)} > Dismiss </button> </div> </div> </div> ) } return ( <div className="fixed top-0 right-0 left-0 text-center z-100"> {message.type === 'success' && <Success />} {message.type === 'error' && <Error />} </div> ) } export default FlashKey Points:
- This component listens to the
FlashContextstate. - It automatically disappears after 4 seconds.
- You can manually dismiss it with the “Dismiss” button.
- Two inner components handle different message types: Success and Error.
Step 3: Create a Page to Trigger Notifications
File:
src/pages/index.tsximport React from "react"; import { FlashContext } from "../context/flash"; export default function Dashboard() { const { setFlash } = React.useContext(FlashContext); return ( <div className="p-25 flex justify-center"> <p><a className='rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white' onClick={() => { setFlash({ message: "Something was done", 'type': "success" }) }}>Set Success</a> <a className='rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white' onClick={() => { setFlash({ message: "Something was wrong", 'type': "error" }) }}>Set Error</a></p> </div> ) }When a user clicks one of these buttons, it updates the FlashContext, which automatically triggers the
Flashcomponent at the top of the screen.Step 4: Wrap the App with
FlashProviderFile:
src/App.tsximport './App.css' import Flash from './components/flash' import { FlashProvider } from './context/flash' import Dashboard from './pages' function App() { return ( <> <FlashProvider> <AppWrapper /> </FlashProvider> </> ) } export default App const AppWrapper = () => { return ( <div className="app-wrapper lg:min-w-[1024px]"> <Dashboard /> <Flash /> </div> ) }- The
FlashProvidermakes theFlashContextavailable throughout the component tree. - Any child (like
Dashboard) can now callsetFlash()to display messages. Apponly handles context setup, not UI.- Reusable Wrapper – easily add more global providers (like AuthProvider or ThemeProvider)
- Consistent Notifications – The
Flashcomponent always stays at the top level, so it’s visible on every page.
- The
- This component listens to the
I have already implemented this notification system in my React project and hosted it on CodeSandbox for reference.
You can explore the full source code and live preview using the following link:
Working Codesandbox: https://codesandbox.io/p/github/InimistTechnologies/react-notification/master (see blow)
Github Repo: https://github.com/InimistTechnologies/react-notification