DEV Community

Oussama Bouyahia
Oussama Bouyahia

Posted on • Edited on

State management in React: Building Dynamic Alert

Introduction:

Alert messages are a common and essential tool that every frontend developer should master, as they play a key role in enhancing the user experience across different parts of an application. While there are various approaches to implementing alert systems, I’ve noticed that many methods often overlook important considerations such as optimization, separation of concerns, and adherence to the DRY (Don’t Repeat Yourself) principle. In this article, I’ll demonstrate a clear and efficient way to implement an alert component using context to ensure reusability and maintainability.
Some developers attempt to create an alert for each component, while others try to invoke an alert component only when needed. Both approaches have their merits, but they can sometimes lead to repetitive or inefficient code.
In this article, I will demonstrate how to create an Alert Context and a dynamic Alert component that allows us to manage its messages and color and control their display throughout the application using typescript and a simple tailwind design.

  1. Create Alert Component

The below component will have two props the message and the color , it is up to you to add a title as well.

The main idea is to set a red color background for failed requests for example and a green for a successful ones.
The style is a simple tailwind style and you can adjust it and add more colors and sections as needed.

import React from "react";

interface AlertProps {
  message: string;
  color: "red" | "green";
}

const Alert: React.FC<AlertProps> = ({ message, color }) => {
  const colorClasses: { [key in AlertProps["color"]]: string } = {
    red: "bg-red-100 border-red-400 text-red-700",
    green: "bg-green-100 border-green-400 text-green-700",
  };

  const selectedColorClass = colorClasses[color];

  return (
    <div
      className={`border-l-4 p-4 ${selectedColorClass} rounded-md shadow-md`}
    >
      <p className="text-md text-center">{message}</p>
    </div>
  );
};

export default Alert;

Enter fullscreen mode Exit fullscreen mode

2.Create Alert Context

This below context will mainly manage the displaying , the message and the color of the Alert.
The purpose of the implementation of useEffect is to handle the time needed before hiding the Alert message.

import {
  useState,
  ReactNode,
  createContext,
  Dispatch,
  SetStateAction,
  useEffect,
} from "react";

 interface AlertFormType {
  message: string;
  color: "green" | "red";
  show: boolean;
}

interface ContextProps {
  children: ReactNode;
}

const defaultValue: AlertValueType = {
  activeAlert: {
    show: false,
    message: "",
    color: "green",
  },
  setActiveAlert: () => {},
};

interface AlertValueType {
  activeAlert: AlertFormType;
  setActiveAlert: Dispatch<SetStateAction<AlertFormType>>;
}
//the defaultValue is just to avoid undefined value when calling useContext
const AlertContext = createContext<AlertValueType>(defaultValue);

const AlertProvider = ({ children }: ContextProps) => {
  const [activeAlert, setActiveAlert] = useState<AlertFormType>({
    show: false,
    message: "",
    color: "green",
  });
  useEffect(() => {
//if the alert message displays wait (02) secondes then hide it
    if (activeAlert.show) {
      const timer = setTimeout(
        () => setActiveAlert((prev) => ({ ...prev, show: false })),
        2000
      );
      return () => clearTimeout(timer);
    }
  }, [activeAlert.show]);

  const alertValue: AlertValueType = { activeAlert, setActiveAlert };

  return (
    <AlertContext.Provider value={alertValue}>{children}</AlertContext.Provider>
  );
};

export { AlertContext, AlertProvider };


Enter fullscreen mode Exit fullscreen mode

3.Calling the Alert component in the Layout

Here the Alert component will be displayed based on the condition invoked from useContext.
If you are not familiar with the layout and the modern react-router-dom, you can just simply understand that the Alert Component should be under your Header component and it will be displayed if one of other component set the show to true.

import { useContext } from "react";
import Header from "./Header";
import { Outlet } from "react-router-dom";
import { AlertContext } from "../Contexts/AlertContext";
import Alert from "./utils Components/Alert";
export default function Layout() {
//destructing the activeAlert from the context
  const { activeAlert } = useContext(AlertContext);
  return (
    <>
      <Header />
//Alert displays based on the show property of the activateAlert object
      {activeAlert.show && (
        <div className="container mx-auto p-4">
          <Alert message={activeAlert.message} color={activeAlert.color} />
        </div>
      )}
      <div className="container mx-auto p-4">
        <Outlet /> //represent the other components
      </div>
    </>
  );
}


Enter fullscreen mode Exit fullscreen mode

4.Example of handling the Alert after a sign in request to the backend

Inside your Login component you can set the activeAlert state depending on the request response.

const Login = () => {
const [userLogin, setUserLogin] = React.useState({ email: "", password: "" });
 const { setActiveAlert } = React.useContext(AlertContext);
//the form event function
const loginSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    axios
      .post("/api/user/login", userLogin)//request to your login route
      .then((res) => {
        //your logic here
         //a successful response alert message will be setted and the background will be green
        setActiveAlert((prev) => ({
          ...prev,
          show: true,
          message: res.data.message,
          color: "green",
        }));

      })
      .catch((err) => {
        if (err.response) {
          setActiveAlert((prev) => ({
            ...prev,
            show: true,
            message: err.response.data.message,
            color: "red",
          }));
          setUserLogin({ email: "", password: "" });
        }
      });
  };
return //your html component here
}
Enter fullscreen mode Exit fullscreen mode

5.Wrapping the App by the context Alert:

Don't forget to wrap your App inside the context, so all children component will have an access to it.

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { AlertProvider } from "./Contexts/AlertContext.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
      <AlertProvider>
        <App />
      </AlertProvider>
  </StrictMode>
Enter fullscreen mode Exit fullscreen mode

Conclusion:

This implementation offers a clean and maintainable approach that adheres to key principles like separation of concerns and the DRY principle. By using TypeScript, we also ensure type safety, reducing the likelihood of unexpected errors in larger, more complex applications. This method not only optimizes code structure but also provides a scalable solution for managing alerts across an application.

Top comments (0)