Moong

[React] Notification custom hook 만들기 - 알림 아이콘, 알림음 설정 본문

React

[React] Notification custom hook 만들기 - 알림 아이콘, 알림음 설정

방울토망토 2023. 4. 19. 22:04

오늘은 아이콘과 알림음을 포함한 알림을 보내줄 수 있는 React custom hook을 만드는 법에 대해 알려드리겠습니다! 🔔

 

 

Notification API

Notification API를 사용하면 웹에서도 사용자들에게 알림을 보내줄 수 있습니다.

📎 Notification 관련 링크

 

알림 예시

 

Notification 객체를 사용하면 쉽게 구현이 가능합니다!

const notification = new Notification("Hi there!");

 

Notification 허용 상태

아래 화면을 다른 사이트들에서 많이 보셨죠!

유저가 해당 웹 사이트의 알림 권한을 허용했는 지에 따라 notification permission의 상태가 달라지게 됩니다.

1️⃣ default : 유저가 허용하지도, 차단하지도 않은 상태입니다.

2️⃣ grant : 유저가 알림을 허용한 상태입니다.

3️⃣ denied : 유저가 알림을 차단한 상태입니다.

 

 

Notification 지원 브라우저

해당 기능을 지원하지 않는 브라우저도 있으니 아래 표를 참고해주세요!

안드로이드는 아직 Notification 기능이 지원되지 않습니다.

 

 

 

Custom Hook

사용자가 알림 권한을 허용해두지 않았을 때 아예 알림이 가지 않으니 계속 권한을 요청하면 좋겠죠!

단순한 알림이 아닌, 알림에 들어가는 아이콘, 텍스트, 또 알림음이 나도록 좀 더 고도화 된 알림을 손쉽게 보내고자

usePushNotification이라는 이름의 custom hook을 만들어 보려고 합니다.

 

1. Notification 상태 확인하기

notification 이 존재하는지 / permission 허용 상태인지 확인합니다.

notification이 존재하지 않으면 존재하지 않는다고 알림을 보내고,

permission이 허용되지 않은 상태이면 requestPermission을 통해 다시 권한을 요청합니다.

    // Notification이 존재하는 지 확인
    if (!Notification) {
      alert("This browser does not support desktop notification");
    }
    // 푸시 알림을 허용하지 않은 경우 - 다시 허용 물어보기
    else if (Notification.permission !== "granted") {
      try {
        Notification.requestPermission().then((permission) => {
          if (permission !== "granted") return;
        });
      } catch (error) {
        if (error instanceof TypeError) {
          Notification.requestPermission().then((permission) => {
            if (permission !== "granted") return;
          });
        } else {
          console.error(error);
        }
      }
    }

 

 

2. Notification type 객체로 관리하기 (선택사항)

상황에 따라 알림을 유형화 해두면 편리합니다.

저는 알림 유형을 두 가지 유형으로 나누고, 알림에 들어갈 이미지와 알림음을 객체로 만들었습니다.

 

1️⃣ DEFAULT : 일반 알림

2️⃣ WARNING : 경고 알림

 

import positiveSoundSource from "../assets/music/positive.mp3";
import warningSoundSource from "../assets/music/warning.wav";

const positiveSound = new Audio(positiveSoundSource);
const warningSound = new Audio(warningSoundSource);
  
const notificationTypeList = [
   { typeId: "WARNING", image: warningImage, sound: warningSound },
   { typeId: "DEFAULT", image: correctImage, sound: positiveSound }
];

 

 

3. 알림 보내기

useRef notificationRef : 알림 객체를 여기에 담아서 관리합니다 -> 클릭 이벤트를 사용할 수 있습니다.

func setNotificationClickEvent : 알림을 클릭하면 해당 알림이 발생한 화면으로 이동합니다.

func fireNotification : 알림 내용, 알림 type 을 인자로 받아 해당 알림을 보냅니다.

 

// 알림을 담을 변수
const notificationRef = useRef();

// 알림 클릭을 제어하는 함수 - 알림이 발생한 화면으로 이동
const setNotificationClickEvent = () => {
    notificationRef.current.onclick = (event) => {
      event.preventDefault();
      window.focus();
      notificationRef.current.close();
    };
};

// 알림 보내는 함수
const fireNotification = (title, alertTypeId = "DEFAULT", options = {}) => {
    let alertType = notificationTypeList.find((el) => el.typeId === alertTypeId);
	
    const newOption = {
      badge: "",
      icon: alertType.image,
      ...options
    };

    // notificationRef에 Notification 객체를 넣음
    notificationRef.current = new Notification(title, newOption);
    
    // 알림음 재생
    alertType.sound.play();

    // click event
    setNotificationClickEvent();
 };

 

 

전체 코드

아래는 전체 코드와 구현 화면입니다.

구현 화면

// App.jsx
import usePushNotification from "./hooks/usePushNotification";
import warningImg from "./assets/images/warning.png";
import correctImg from "./assets/images/correct.png";
import "./styles.css";

export default function App() {
  const { fireNotification } = usePushNotification();

  return (
    <div className="App">
      <h1>NOTIFICATION</h1>
      <div className="description">click the buttons!</div>
      <div className="button-wrapper">
        <button
          onClick={() => {
            fireNotification("It's Warning!", "WARNING");
          }}
        >
          <img src={warningImg} alt="warning" />
          Alert Warning
        </button>
        <button
          onClick={() => {
            fireNotification("It's Notification!", "DEFAULT");
          }}
        >
          <img src={correctImg} alt="notificate" />
          Notificate Something
        </button>
      </div>
    </div>
  );
}
// usePushNotification.js
import { useEffect, useRef } from "react";
import warningImage from "../assets/images/warning.png";
import correctImage from "../assets/images/correct.png";
import positiveSoundSource from "../assets/music/positive.mp3";
import warningSoundSource from "../assets/music/warning.wav";

const positiveSound = new Audio(positiveSoundSource);
const warningSound = new Audio(warningSoundSource);

const usePushNotification = () => {
  const notificationRef = useRef(null);

  const notificationTypeList = [
    { typeId: "WARNING", image: warningImage, sound: warningSound },
    { typeId: "DEFAULT", image: correctImage, sound: positiveSound }
  ];

  useEffect(() => {
    // Notification 지원되지 않는 브라우저 대응
    if (!Notification) {
      alert("This browser does not support desktop notification");
    }
    // 푸시 알림을 허용하지 않은 경우 - 다시 허용 물어보기
    else if (Notification.permission !== "granted") {
      try {
        Notification.requestPermission().then((permission) => {
          if (permission !== "granted") return;
        });
      } catch (error) {
        if (error instanceof TypeError) {
          Notification.requestPermission().then((permission) => {
            if (permission !== "granted") return;
          });
        } else {
          console.error(error);
        }
      }
    }
  }, []);

  // 유저가 림을 클릭하면, 푸시 알림이 일어난 화면으로 이동
  const setNotificationClickEvent = () => {
    notificationRef.current.onclick = (event) => {
      event.preventDefault();
      window.focus();
      notificationRef.current.close();
    };
  };

  const fireNotification = (title, alertTypeId = "DEFAULT", options = {}) => {
    let alertType = notificationTypeList.find((el) => el.typeId === alertTypeId);

    const newOption = {
      badge: "",
      icon: alertType.image,
      ...options
    };
    alertType.sound.play();

    // notificationRef에 Notification 객체를 넣음
    notificationRef.current = new Notification(title, newOption);

    // click event
    setNotificationClickEvent();
  };

  return { fireNotification };
};

export default usePushNotification;

 

 

CodeSandBox

해당 코드를 code sand box에 올려두었습니다.

아래 링크로 들어가서 알림 허용을 하시고 버튼을 누르면 알림을 확인하실 수 있습니다!

https://ugt8vm.csb.app/

 

 

Comments