PJH's live chat - Modal

박정호·2023년 2월 5일
0

Live Chat Project

목록 보기
4/7
post-thumbnail

🚀 Start

이 앱에는 여러 종류의 모달창이 존재한다.

  • 워크스페이스 생성 모달창
  • 채널 생성 모달창
  • 워크스페이스 초대 모달창
  • 채널 초대 모달창

이 모달창들은 공통된 디자인과 기능을 가진 부분들이 있기 때문에, 공통 부분은 Modal 컴포넌트에 관리되며 4개의 모달창들이 children 컴포넌트로 들어가게 된다.

⭐️ client
|
└── 🗂 components
	 ├── 🗂 Modal
     ├── 🗂 CreateWorkSpaceModal
     ├── 🗂 CreateChannelSpaceModal
     ├── 🗂 InviteWorkSpaceModal
     └── 🗂 InviteChannelModal




⚙️ Event.stopPropagation()

모달창 구현에 앞서 가장 중요한 부분이 한가지 있다. 그 것은 모달창 닫기 버튼 이외에도 모달창의 다른 부분들을 클릭하면 자동으로 모달창이 닫혀 사용자 경험을 개선시키는 것이다.

나는 이 방법을 구현하기 위해 CSS, ref 등의 방법들을 생각했던 것 같은데, 이번에 제대로 좋은 기능을 알게 되었다.

제일 상위 요소에 닫기 기능을 동작할 수 있게 한다. 즉, 어디를 누르던 모달창이 닫힐 것이다. 하지만, 그 뜻은 모달창을 클릭했을 때에도 닫기 기능이 동작한다는 것이다.

이처럼 하위 요소의 이벤트의 발생으로 상위 요소까지 모두 영향을 받게 되는 이벤트 버블링이 발생하게 된다.

이벤트의 전파를 원하지 않는다면 event.stopPropagation()라는 이벤트 객체 메서드를 사용하면 된다.

// Modal.tsx
const stopPropagation = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    e.stopPropagation();
  }, []);

<CreateModal onClick={onCloseModal}>
   <div onClick={stopPropagation}>
      <CloseModalButton onClick={onCloseModal}>&times;</CloseModalButton>
      {children} // 역할에 따른 다른 모달창 기능 
  </div>
</CreateModal>


✔️ CreateWorkSpaceModal

이 모달창은 왼쪽 사이드바의 워크스페이스 추가 버튼을 클릭시 출력되는 모달창이다.

const CreateWorkSpaceModal: FC<Props> = ({show, onCloseModal, setShowCreateWorkspaceModal,
}) => {
	...
  const [newWorkspace, onChangeNewWorkspace, setNewWorkspace] = useInput('');
  const [newUrl, onChangeNewUrl, setNewUrl] = useInput('');

  const onCreateWorkspace = useCallback(e => {
      e.preventDefault();
    
      if (!newWorkspace || !newWorkspace.trim()) return;
      if (!newUrl || !newUrl.trim()) return;
   
      axios
        .post('/api/workspaces',{ workspace: newWorkspace, url:newUrl})
        .then(() => {
          revalidateUser();
          setShowCreateWorkspaceModal(false);
          setNewWorkspace('');
          setNewUrl('');
        })
        .catch(error => {
          console.dir(error);
          toast.error(error.response?.data, { position: 'bottom-center' });
        });
    },
    [newWorkspace, newUrl]
  );

  if (!show) return null;
  
  return (
    <Modal show={show} onCloseModal={onCloseModal}>
        <form onSubmit={onCreateWorkspace}>
          
          <Input ... onChange={onChangeNewWorkspace}/> //워크스페이스 이름
          <Input ... onChange={onChangeNewUrl}/> // 워크스페이스 url
          <Button type="submit">생성하기</Button> // 워크스페이스 생성

        </form>
    </Modal>
  );
};


✔️ CreateChannelSpaceModal

const CreateChannelModal: FC<Props> = ({ show,onCloseModal,setShowCreateChannelModal,
}) => {
	...
  const { mutate: revalidateChannel } = useSWR<IChannel[]>(
    userData ? `/api/workspaces/${workspace}/channels` : null, fetcher
  );

  const onCreateChannel = useCallback(e => {
      
    e.preventDefault();
    if (!newChannel || !newChannel.trim()) return;
     
    axios.post(`/api/workspaces/${workspace}/channels`,{name:newChannel})
        .then(() => {
          revalidateChannel();
          setShowCreateChannelModal(false);
          setNewChannel('');
        })
        .catch(error => {
          console.dir(error);
          toast.error(error.response?.data, { position: 'bottom-center' });
        });
    },
    [newChannel,revalidateChannel,setNewChannel,setShowCreateChannelModal,workspace]
  );

  return (
    <Modal show={show} onCloseModal={onCloseModal}>
      <form onSubmit={onCreateChannel}>
        <Input ... onChange={onChangeNewChannel}/>
        <Button>생성하기</Button>
      </form>
    </Modal>
  );
};


✔️ InviteWorkSpaceModal

const InviteWorkspaceModal: FC<Props> = ({show,onCloseModal,setShowInviteWorkspaceModal}) => {
 	...
  const { mutate: revalidateMember } = useSWR<IUser[]>(
    userData ? `/api/workspaces/${workspace}/members` : null,fetcher
  );

  const onInviteMember = useCallback(e => {
      
    e.preventDefault();
    if (!newMember || !newMember.trim()) return;
   
    axios.post(`/api/workspaces/${workspace}/members`,{email: newMember})
        .then(() => {
          revalidateMember();
          setShowInviteWorkspaceModal(false);
          setNewMember('');
        })
        .catch(error => {
          console.dir(error);
          toast.error(error.response?.data, { position: 'bottom-center' });
        });
    },
    [newMember,workspace,revalidateMember,setShowInviteWorkspaceModal,setNewMember]
  );

  return (
    <Modal show={show} onCloseModal={onCloseModal}>
      <form onSubmit={onInviteMember}>
         <Input ... onChange={onChangeNewMember}/>
         <Button type="submit">초대하기</Button>
      </form>
    </Modal>
  );
};


✔️ InviteChannelModal

const InviteChannelModal: FC<Props> = ({show,onCloseModal,setShowInviteChannelModal
}) => {
  	...
  const { mutate: revalidateMembers } = useSWR<IUser[]>(
    userData ? `/api/workspaces/${workspace}/channels/${channel}/members` : null,
    fetcher
  );

  const onInviteMember = useCallback(e: => {
      
     e.preventDefault();
     if (!newMember || !newMember.trim()) return;
 
     axios.post(`/api/workspaces/${workspace}/channels/${channel}/members`, {email: newMember})
        .then(() => {
          revalidateMembers();
          setShowInviteChannelModal(false);
          setNewMember('');
        })
        .catch(error => {
          console.dir(error);
          toast.error(error.response?.data, { position: 'bottom-center' });
        });
    },
    [channel,newMember,revalidateMembers,setNewMember,setShowInviteChannelModal,workspace]
  );

  return (
    <Modal show={show} onCloseModal={onCloseModal}>
      <form onSubmit={onInviteMember}>
      <Input ... onChange={onChangeNewMember} placeholder="초대할 이메일을 입력하세요"/>
      <Button type="submit">초대하기</Button>
      </form>
    </Modal>
  );
};
profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글