DEV Community

Ayomikun Sholademi
Ayomikun Sholademi

Posted on

How to create a custom alert component in REACT with Context API

Do you have to implement a feature that requires you to prompt a user for response (for example the confirmation message before deleting an item)? Or do you want to have a "awesome" designer who designed an awesome component for notifying users on specific actions? Or for the hundred other reasons you have decided to read this article.
In this article, We will be implementing a custom alert component for prompting a user to perform an action or notifying a user using ReactJS with Context API.

PREREQUISITES

This tutorial makes use of the following languages/technologies and would require you to have a basic knowledge of their usage.

  • TypeScript
  • ReactJS (bootstrapped with create-react-app)
  • CSS

Setting up

Diving into creating the alert component, the first thing you need to do is set up your codebase. Since we are going to be making use of ReactJS you can use any of the React Libraries that are available, but for the purpose of this tutorial, we will be bootstrapping out react application with create-react-app

Structuring the Codebase.

After setting up your codebase, you will proceed to structure the codebase as follows.

  • Create a folder named components inside the src folder.
  • In the components folder, create a new folder named Alert.
  • Inside the Alert folder, create a file named Alert.tsx.

Copy the following code and paste in your Alert.tsx file

// the component UI will live in this file

const Alert = () => {
  return (
    <></>
  );
};

export default Alert;

Enter fullscreen mode Exit fullscreen mode
  • Next up, you create a file in the Alert folder named AlertProvider.tsx. Inside the AlertProvider.tsx file, paste the following code.
import { createContext, useRef, useState } from "react";

export const AlertContext = createContext();

const AlertProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  return (
    <AlertContext.Provider
      value={}
    >
      {children}
    </AlertContext.Provider>
  );
};

export default AlertProvider;

Enter fullscreen mode Exit fullscreen mode

Next up, you create a file named index.tsx inside the Alert folder.
Inside the index.tsx file, paste the following code;

Here you are creating a wrapper for the alert context for the alert component and the children to subscribe to the context changes.

import { useContext } from "react";
import AlertProvider, { AlertContext } from "./AlertProvider";
import Alert from "./Alert";

// create hook to be used globally to toggle alert component.
export const useAlert = () => useContext(AlertContext);

const AlertContainer: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  return (
    <AlertProvider>
      <Alert />
      {children}
    </AlertProvider>
  );
};

export default AlertContainer;

Enter fullscreen mode Exit fullscreen mode

Register Alert Context in entry file

Now that you have created the context for the alert component, you will register the context globally by updating the entry file. For this tutorial, you will update the src/index.tsx file. If you are using other frameworks, your entry file might be different (for NextJS, the entry will would be /pages/_app.tsx)

Update your src/index.tsx file with the following code.

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import AlertContainer from "./components/Alert"; // import the AlertContainer

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    {/* Wrap your application with the Alert context wrapper you created in `/components/Alert/index.tsx' */}
    <AlertContainer>
      <App />
    </AlertContainer>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Enter fullscreen mode Exit fullscreen mode

Implement functionality for the Alert component.

Now that you are done setting up, you will now implement the functionalities for your alert component.
In this tutorial, the major functionalities you will be implementing are

  • Open alert component: this will cover the different states in which your component will be rendered. Either as a notifier or as a prompter.
  • Close alert component: this will cover removing the alert component from user's view and reverting the context state to default.

Now start updating the src/components/AlertProvider.tsx file.

First, you need to declare the types that will be used in the functionality.
So your file will look like this;

import { createContext, useRef, useState } from "react";

// type for notification object
interface IAlertNotification {
  title: string;
  message: string | React.ReactNode;
  delay?: number;
  autoClose?: boolean;
  inputProps?: React.HTMLAttributes<HTMLInputElement>;
  actions?: {
    text: string;
    callback?: (arg: any) => void;
    props?: React.HTMLAttributes<HTMLButtonElement>;
  }[];
}

// the types for methods and variable available in your alert context
interface IAlertContext {
  alert: (args: IAlertNotification) => void;
  alertShown?: boolean;
  notification?: IAlertNotification;
  close: () => void;
}

// intialise default methods for context.
export const AlertContext = createContext<IAlertContext>({
  alert: () => {},
  close: () => {},
});

const AlertProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  return (
    <AlertContext.Provider
      value={}
    >
      {children}
    </AlertContext.Provider>
  );
};

export default AlertProvider;

Enter fullscreen mode Exit fullscreen mode

Next up, you will create the necessary variables and methods to display and hide the alert component.
So your file will look like this:

import { createContext, useRef, useState } from "react";

// type for notification object
interface IAlertNotification {
  title: string;
  message: string | React.ReactNode;
  delay?: number;
  autoClose?: boolean;

  inputProps?: React.HTMLAttributes<HTMLInputElement>;
  actions?: {
    text: string;
    callback?: (arg: any) => void;
    props?: React.HTMLAttributes<HTMLButtonElement>;
  }[];
}
// the types for methods and variable available in your alert context
interface IAlertContext {
  alert: (args: IAlertNotification) => void;
  alertShown?: boolean;
  notification?: IAlertNotification;
  close: () => void;
}

// initialize default methods for context.
export const AlertContext = createContext<IAlertContext>({
  alert: () => {},
  close: () => {},
});

const AlertProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [alertShown, setAlertShown] = useState(false); // toggles the view state of the alert component
  const [notification, setNotification] = useState<
    IAlertNotification | undefined
  >(); // stores the configuration data for the alert component
  const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(); // stores the timer value for autoclosing the alert component

  // closes the alert component and reverts all config to default values.
  const close = () => {
    setAlertShown(false);
    setNotification(undefined);
    clearTimeout(timerRef.current);
  };

  // opens the alert component and configures its view state.
  const alert = (args: IAlertNotification) => {
    setNotification(args);
    setAlertShown(true);

    if (args.autoClose) {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
      }

      timerRef.current = setTimeout(() => {
        close();
      }, notification?.delay || 3000);
    }

    return notification;
  };

  return (
    <AlertContext.Provider
      value={{
        notification,
        alert,
        alertShown,
        close,
      }}
    >
      {children}
    </AlertContext.Provider>
  );
};

export default AlertProvider;
Enter fullscreen mode Exit fullscreen mode

Implement Alert component UI.

Now that you have implemented the functionality, the next step is to create the component UI and set up the view states based on the configuration in the context.

Copy the following code and paste it in the src/components/Alert/Alert.tsx

import { useState } from "react";
import { useAlert } from ".";
import styles from "./styles.module.css";

const Alert = () => {
  const { notification, alertShown, close } = useAlert();
  const [textInputValue, setTextInputValue] = useState("");

  return (
    <div
      className={`
        ${styles.alert_container} ${alertShown ? styles.show : ''}`}
    >
      <div className={styles.alert}>
        <div className={styles.alert_content}>
          <p className={styles.alert_title}>{notification?.title || ""}</p>

          <div className={styles.alert_body}>
            <p>{notification?.message || ""}</p>
          </div>

          {notification?.inputProps && (
            <div className={styles.alert_textinput}>
              <input
                value={textInputValue}
                onChange={(e) => setTextInputValue(e.target.value)}
                {...notification?.inputProps}
              />
            </div>
          )}

          <div className={styles.alert_action}>
            {(notification?.actions || []).map((action) => (
              <div key={action.text} className={styles.btn}>
                <button
                  {...action.props}
                  onClick={() => {
                    action.callback?.(textInputValue);
                    setTextInputValue("");
                    close();
                  }}
                >
                  {action.text}
                </button>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

export default Alert;
Enter fullscreen mode Exit fullscreen mode

Next up, you style the component.
Create a file named styles.module.css in the src/component/Alert folder.

Copy the following code and paste it in the styles.module.css file

.alert_container {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  height: 100%;
  width: 100%;
  z-index: 999;
  transform: translateY(-100%);
  transition: all 500ms;
}
.alert_container.show {
  transform: translateY(0px);
}

.alert {
  width: 100%;
  max-width: 500px;
  margin: 0 auto;
  height: auto;
}

.alert_content {
  background-color: white;
  border-radius: 0px 0px 10px 10px;
  padding: 20px;
  width: 100%;
  box-shadow: 0px 10px 50px rgba(255, 255, 255, 0.3);
}

.alert_title {
  font-weight: 800;
  font-size: 1.275rem;
}

.alert_body {
  margin-top: 10px;
  font-size: 1rem;
}

.alert_textinput {
  margin-top: 20px;
  display: flex;
}

.alert_textinput input {
  width: 100%;
  height: 40px;
  border-radius: 8px;
  border: 1px solid grey;
  padding: 0px 10px;
}

.alert_action {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-top: 20px;
}

.alert_action .btn {
  flex: 1;
}

.alert_action .btn button {
  height: 40px;
  background-color: #282c34;
  border: 0px;
  border-radius: 8px;
  width: 100%;
  font-weight: 600;
  color: white;
  cursor: pointer;
}

Enter fullscreen mode Exit fullscreen mode

Testing the alert component

Now that you have finished implementing the component, you can test the various variations for prompting and notifying the users.

Copy the following code and replace your app.tsx file with it.

import logo from "./logo.svg";
import "./App.css";
import { useAlert } from "./components/Alert";

function App() {
  const { alert } = useAlert();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <button
          onClick={() =>
            alert({
              title: "Notify",
              message: "Notification message",
              autoClose: true,
            })
          }
        >
          Notify
        </button>

        <button
          onClick={() =>
            alert({
              title: "Prompt without feedback",
              message: "Question to ask user before proceeding",
              actions: [
                {
                  text: "Cancel",
                },
                {
                  text: "Confirm",
                  callback: () => {
                    console.log('Confirmed')
                    // action to proceed
                  },
                },
              ],
            })
          }
        >
          Prompt without Feedback
        </button>
        <button
          onClick={() =>
            alert({
              title: "Prompt with feedback",
              message:
                "Question to ask user before proceeding. Expecting message from user",
              inputProps: { placeholder: "Enter feedback" },
              actions: [
                {
                  text: "Cancel",
                },
                {
                  text: "Confirm",
                  callback: (feedback) => {
                    console.log(feedback);
                    // action to proceed
                  },
                },
              ],
            })
          }
        >
          Prompt with Feedback
        </button>
      </header>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Results!!!

React alert component notification only

React alert component prompt without feedback

React alert component prompt with feedback

Conclusion.

Yayyy, you have successfully implemented a custom alert component in React. I know the article was a bit lengthy, if you have any questions, be sure to leave them in the comments and I will respond.
I have also taken the liberty to push the code to github. You can access it [here](https://github.com/haywhy43/create-custom-alert-component-with-react}

Top comments (1)

Collapse
 
cezarytomczyk profile image
Cezary Tomczyk

Native, built-in <dialog> element would be a better option.

developer.mozilla.org/en-US/docs/W...