4주차 - 댓글 알림 뱃지 만들기
본문 바로가기
항해 중/5주차 리액트 심화반

4주차 - 댓글 알림 뱃지 만들기

by 은돌1113 2021. 12. 3.

댓글 알람은 언제 와야 할까??

(1) 내가 쓴 게시글에 (2) 내가 아닌 사람이 (3) 댓글을 쓰면 알려준다.

 

댓글 알람에는 뭐가 필요할까??

: 알람을 읽었는 지 안읽었는 지 구분자가 필요합니다!

 

1. 파이어베이스의 실시간 데이터베이스를 만들기(realtime database)

: 구분자로는 특정 할 수 있는 값을 사용하면 됩니다. (예를 들어서 아이디, 핸드폰 번호 등등)

https://console.firebase.google.com/project/imagecommunity-aabf8/database/imagecommunity-aabf8-default-rtdb/data

 

로그인 - Google 계정

하나의 계정으로 모든 Google 서비스를 Google 계정으로 로그인

accounts.google.com


2. 댓글 알림 뱃지를 material ui로 만들기

 

- 공식 문서

https://mui.com/

 

MUI: The React component library you always wanted

MUI provides a simple, customizable, and accessible library of React components. Follow your own design system, or start with Material Design. You will develop React applications faster.

mui.com

 

- 패키지 설치

yarn add @material-ui/core @material-ui/icons

 

- NotiBadge.js 만들기

: maretial ui의 아이콘과 Badge를 만든다.

//components/NotiBadge.js
import React from "react";

import { Notifications } from "@material-ui/icons";
import { Badge } from "@material-ui/core";

const NotiBadge = (props) => {
  const [is_read, setIsRead] = React.useState(true);

  const notiCheck = () => {
    props._onClick();
  };

  return (
    <React.Fragment>
      <Badge
        invisible={is_read}
        color="secondary"
        onClick={notiCheck}
        variant="dot"
      >
        <Notifications />
      </Badge>
    </React.Fragment>
  );
};

NotiBadge.defaultProps = {
  _onClick: () => {},
};

export default NotiBadge;
// Header.js
import NotiBadge from "./NotiBadge";

const Header = (props) => {
...
  if (is_login && is_session) {
    return (
      <React.Fragment>
        <Grid is_flex padding="4px 16px">
          <Grid>
            <Text margin="0px" size="24px" bold>
              헬로
            </Text>
          </Grid>

          <Grid is_flex>
            <Button text="내정보"></Button>
            <NotiBadge _onClick={() => {
              history.push("/noti");
            }}/>
            <Button
              text="로그아웃"
              _onClick={() => {
                dispatch(userActions.logoutFB());
              }}
            ></Button>
          </Grid>
        </Grid>
      </React.Fragment>
    );
  }

3. realtime database 구독해서 읽음 처리하기

 

- realtime database 리스너 구독하기

더보기

👉 무한 스크롤은 클래스형 컴포넌트를 사용해서 스크롤 이벤트를 구독했던 거, 기억하시죠! 😎
오늘은 realtime database의 리스너를 구독하고 해제해봅시다! → useEffect()를 사용할거예요!

1) firebase.js에 realtime database 추가하기

// shared/firebase.js

import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import "firebase/storage";
import "firebase/database";

const firebaseConfig = {
  //인증정보!
};

firebase.initializeApp(firebaseConfig);

const apiKey = firebaseConfig.apiKey;
const auth = firebase.auth();
const firestore = firebase.firestore();
const storage = firebase.storage();
const realtime = firebase.database();

export{auth, apiKey, firestore, storage, realtime};

 

2) <NotiBadge/>에서 리스너 구독하기

import React from "react";
import { Badge } from "@material-ui/core";
import NotificationsIcon from "@material-ui/icons/Notifications";

import {realtime} from "../shared/firebase";
import {useSelector} from "react-redux";

const NotiBadge = (props) => {
  const [is_read, setIsRead] = React.useState(true);
  const user_id = useSelector(state => state.user.user.uid);
  const notiCheck = () => {
      props._onClick();
  };

  React.useEffect(() => {
    const notiDB = realtime.ref(`noti/${user_id}`);

    notiDB.on("value", (snapshot) => {
        console.log(snapshot.val());
        
        setIsRead(snapshot.val()?.read);
        // 옵셔널 체이닝을 사용함.
    });
  }, []);

  return (
    <React.Fragment>
      <Badge color="secondary" variant="dot" invisible={is_read} onClick={notiCheck}>
        <NotificationsIcon />
      </Badge>
    </React.Fragment>
  );
};

NotiBadge.defaultProps = {
  _onClick: () => {},
};

export default NotiBadge;

 

- 구독 해제하기

더보기

👉 리스너 구독 해제는 컴포넌트가 unMount 되는 시점에 해야하죠!

useEffect의 return에 넘겨주는 함수는 컴포넌트가 사라질 때 실행됩니다!

React.useEffect(() => {
   ...
    // 리스너를 해제합시다!
    return () => notiDB.off();
  }, []);

 

- 댓글 달면 알림을 주자!

// redux/modules/comment.js

const addCommentFB = (post_id, contents) => {
  return function (dispatch, getState, { history }) {
    const commentDB = firestore.collection("comment");
    const user_info = getState().user.user;

    let comment = {
      post_id: post_id,
      user_id: user_info.uid,
      user_name: user_info.user_name,
      user_profile: user_info.user_profile,
      contents: contents,
      insert_dt: moment().format("YYYY-MM-DD hh:mm:ss"),
    };

    // firestore에 코멘트 정보를 넣어요!
    commentDB.add(comment).then((doc) => {
      const postDB = firestore.collection("post");
      comment = { ...comment, id: doc.id };

      // post 정보 가져오기!
      const post = getState().post.list.find((l) => l.id === post_id);

      //   firestore에 저장된 값을 +1해줍니다!
      const increment = firebase.firestore.FieldValue.increment(1);

      // post에도 comment_cnt를 하나 플러스 해줍니다.
      postDB
        .doc(post_id)
        .update({ comment_cnt: increment })
        .then((_post) => {
          // comment를 추가해주고,
          dispatch(addComment(post_id, comment));

          // 리덕스에 post가 있을 때만 post의 comment_cnt를 +1해줍니다.
          if (post) {
            dispatch(
              postActions.editPost(post_id, {
                comment_cnt: parseInt(post.comment_cnt) + 1,
              })
            );
            
            // 알림이 가게 해줍니다!
            const notiDB = realtime.ref(`noti/${post.user_info.user_id}`);
            // 읽음 상태를 false로 바꿔주면 되겠죠!
            notiDB.update({ read: false});
          }
        });
    });
  };
};

 

- 노티 아이콘 누르면 읽음 처리하기

// components/NotiBadge.js
	const notiCheck = () => {
    notiDB.update({ read: true });
    props._onClick();
  };

4. 알림 페이지에서 알림 확인하기

더보기

👉 실시간 데이터베이스에 알림 내역 리스트를 만들고, 알림페이지에서 리스트를 확인하도록 해봅시다! 🙂

알림 내역에는 댓글을 쓴 유저의 user_name, post_id, image_url이 필요해요!

앗, 그리고 insert_dt도 넣어줘야 정렬 해주기 좋겠죠!

 

- 알림 내역을 저장하기

// redux/modules/comment.js
const addCommentFB = (post_id, contents) => {
  return function (dispatch, getState, { history }) {
    ...

      // post에도 comment_cnt를 하나 플러스 해줍니다.
      postDB
        .doc(post_id)
        .update({ comment_cnt: increment })
        .then((_post) => {
          // comment를 추가해주고,
          dispatch(addComment(post_id, comment));

          // 리덕스에 post가 있을 때만 post의 comment_cnt를 +1해줍니다.
          if (post) {
            dispatch(
              postActions.editPost(post_id, {
                comment_cnt: parseInt(post.comment_cnt) + 1,
              })
            );

            // 알림 리스트에 하나를 추가해줍니다!
            const _noti_item = realtime
              .ref(`noti/${post.user_info.user_id}/list`)
              .push();

            _noti_item.set({
              post_id: post.id,
              user_name: comment.user_name,
              image_url: post.image_url,
              insert_dt: comment.insert_dt
            }, (err) => {
                if(err){
                    console.log('알림 저장 실패');
                }else{
                  // 알림이 가게 해줍니다!
                  const notiDB = realtime.ref(`noti/${post.user_info.user_id}`);
                  // 읽음 상태를 false로 바꿔주면 되겠죠!
                  notiDB.update({ read: false });
                }
            });
          }
        });
    });
  };
};

 

- 알림 페이지에서 알림 list 가져오기

// pages/Notification.js
import React from "react";
import {Grid} from "../elements";
import Card from "../components/Card";

import {realtime} from "../shared/firebase";
import {useSelector} from "react-redux";

const Notification = (props) => {
  const [noti, setNoti] = React.useState([]);

    const user = useSelector(state => state.user.user);

    React.useEffect(() => {

      if(!user){
        return;
      }

      const notiDB = realtime.ref(`noti/${user.uid}/list`);
      
      // firebase realtime database는 내림차순 정렬을 지원하지 않아요!
      // 데이터를 가져온 후 직접 역순으로 내보내야 합니다!
      const _noti = notiDB.orderByChild("insert_dt");
      
      _noti.once('value', snapshot => {
        
        if(snapshot.exists()){
          let _data = snapshot.val();

          // reserse()는 배열을 역순으로 뒤집어줘요.
          let _noti_list = Object.keys(_data).reverse().map(s => {
            return _data[s];
          });

          setNoti(_noti_list);
        }
        
      })
    }, [user]);

    return (
      <React.Fragment>
        <Grid padding="16px" bg="#EFF6FF">
          {noti.map((n, idx) => {
            return <Card {...n} key={`noti_${idx}`} />;
          })}
        </Grid>
      </React.Fragment>
    );
}

export default Notification;

 

- 알림창에서 알림 하나를 누르면 게시물 상세 페이지로 이동하기

// components/Card.js

import React from "react";
import { Grid, Text, Image } from "../elements";

import {history} from "../redux/configureStore";

const Card = (props) => {
  const { image_url, user_name, post_id } = props;

  return (
     {history.push(`/post/${post_id}`);}}
    >
      
        
      
      
        
          {user_name}님이 게시글에 댓글을 남겼습니다 :)!{" "}
        
      
    
  );
};

Card.defaultProps = {
  image_url: "<http://via.placeholder.com/400x300>",
};

export default Card;

댓글