Toast Message

Toast Message

Toast Message는 모바일에서든 웹에서든 자주 볼 수 있는 notification 스타일입니다. velog에 글을 쓸 때도 자주 만나볼 수 있어요.

혼자 과제하는 시간이 길어지다보니 이런저런 기능들을 넣고 싶은 욕심이 생깁니다;

localStorage에 저장할 수 있는 아이템 수를 정해두고, 유저가 그 이상 저장을 시도할 경우 alert창이 뜨도록 해뒀는데 그게 왜 그렇게 안 예뻐보이는지...ㅜㅜ

그래서 Toast message를 만들어보기로 했습니다.

1. 컴포넌트 만들기

2개의 컴포넌트가 필요했습니다.
배열로 쌓이는 noti-list 상태관리를 해줄 notification과 Toast 컴포넌트입니다.

Toast.js는 사라졌는지 아닌지 상태관리만 두고 setInterval을 이용해 지정된 시간이 지나면 사라지도록 했습니다.

toast는 아래 객체를 받아 보여주도록 했습니다.

let noti = {
  id,
  type: 'success' or 'danger',
  description,
  dismissalTime
}

처음 noti가 뜰 때는 애니메이션을 줬습니다.

const Toast = ({ item }) => {
  const [isFading, setIsFading] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => {
      setIsFading(true);
    }, item.dismissalTime);
    return () => {
      clearInterval(interval);
    };
  }, [item.dismissalTime]);

  return (
    <ToastMessage isFading={isFading}>
      {item.description}
    </ToastMessage>
  );
};

export default Toast;

const show = keyframes`
  from {
    transform: translateX(100%);
  } to {
    opacity: 1;
    transform: translateX(0px);
  }
`;

const ToastMessage = styled.div`
  animation: ${show} 1.5s ease-in-out;

  ${(props) =>
    props.isFading &&
    css`
      opacity: 0;
      transform: opacity 1.5s ease-in-out;
    `}
`;

Notification.js는 단순합니다.
props를 쌓아서 상태관리를 해주고, list 배열을 돌며 Toast message가 만들어지도록 했습니다.

const Notification = ({ notification }) => {
  const [toastList, setToastList] = useState();

  useEffect(() => {
    setToastList([...notification]);
  }, [notification]);

  return (
    <Notifications>
      {toastList?.map((item, index) => (
        <Toast key={index} item={item} onDelete={onDelete} />
      ))}
    </Notifications>
  );
};

2. 컴포넌트 연결하기

이벤트가 발생할 때에 맞게, 각 키에 값을 할당해 list에 넣어줍니다.

const onDeleteRepo = (repo) => {
  ...
  ...
  let noti = {
    id: id,
    type: 'success',
    description: 'Repository를 삭제했습니다.',
    dismissalTime: 3000,
  };
  setNotification([...notification, noti]);
  ...
  ...
};

타입별로 배경색이 다르게 적용해줍니다.


<ToastMessage isFading={isFading} type={`${item.type}`}>
  {item.description}
</ToastMessage>

const ToastMessage = styled.div`
	....
	...
  background-color: ${(props) =>
    props.type === 'success' ? '#5cb85c' : '#d9534f'};
	...
`

3. 완성

notification ui만 변경했을 뿐인데 프로젝트가 갑자기 퀄리티가 올라간 느낌입니다.
다시 한 번 ui의 중요성을 느낍니다.

(추가) Redux

리덕스를 추가하면서 notification을 위한 별도의 reducer를 만들었습니다.

const initialState = { data: [] };

export const notiReducer = createSlice({
  name: 'notifications',
  initialState,
  reducers: {
    addNoti: (state, actions) => {
      const { type, description, dismissalTime } = actions.payload;
      let item = {
        id: Date.now(),
        type: type,
        description: description,
        dismissalTime: dismissalTime ? dismissalTime : 3000,
      };
      state.data = [...state.data, item];
    },
  },
});

Toast Message를 만들면서 제일 신경쓰였던 것은 notification item객체가 배열에 계속 쌓인다는 것이었습니다.
이렇게 되면 페이지를 이동했다가 다시 현재 페이지로 돌아왔을 때 아래 보이는 사진처럼 알림이 한꺼번에 브라우저에 등장합니다;

어떻게든 저 배열들을 지우고 싶다....
강력한 욕구가 샘솟았습니다.

1. 언제 어떻게 삭제하나

알림이 곧장 삭제되거나 제대로 삭제되지 않는다면, 사용자 입장에서 굉장히 부자연스러운 알람을 볼 수 밖에 없습니다.

처음엔 별 생각없이 배열이 5개 이상 쌓이면 삭제되도록 했는데, 아래 처럼 한꺼번에 삭제되는 알람을 볼 수 있었습니다.

2. Reducer에 삭제 기능 추가

배열이 6개가 되는 시점에 이전 배열 5개가 삭제되도록 리듀서를 추가했습니다.

clearNoti: (state) => {
  state.data.splice(0, 6);
},

배열이 6개가 되는 시점에 앞서 5개의 배열이 삭제 되도록해뒀기 때문에 그 시간을 계산해 5개 알람이 모두 보여졌다가 사라진 시간 이후 배열이 삭제 되도록 함수를 추가했습니다.

const spliceArray = useCallback(() => {
  setInterval(() => {
    dispatch(clearNoti());
  }, 18000);
}, [dispatch]);

useEffect(() => {
  if (notification?.length > 5) {
      spliceArray();
}
  return () => {
  	clearInterval(spliceArray);
};
}, [notification, spliceArray]);

자연스러운 알람을 볼 수 있습니다.(만세)

좋은 웹페이지 즐겨찾기