Using Context API to create text alert or notification at the top of a page

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 FlashContext to share the current notification message and a setter function.
  • The provider wraps our app so any component can show a flash message.
  • The message has 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.tsx

      import 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 Flash

      Key Points:

      • This component listens to the FlashContext state.
      • 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.tsx

        import 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 Flash component at the top of the screen.

        Step 4: Wrap the App with FlashProvider

        File: src/App.tsx

        import './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 FlashProvider makes the FlashContext available throughout the component tree.
        • Any child (like Dashboard) can now call setFlash() to display messages.
        • App only handles context setup, not UI.
        • Reusable Wrapper –  easily add more global providers (like AuthProvider or ThemeProvider)
        • Consistent Notifications – The Flash component always stays at the top level, so it’s visible on every page.

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

Leave a Reply