워크스페이스 gravatar
사용자 프로필 이미지 아이콘 '그라바타' '기타' '절소스콘' 서비스
이메일 이미지 1대1매칭(아이덴티티)
npm i gravatar
npm i @types/gravatar -> 타입스크립트 용 그라바타 설치
<RightMenu>
{/* 사용자프로필이미지 */}
<span onClick={onClickUserProfile}>
<ProfileImg src={gravatar.url(userData.email, { s: '28px', d: 'retro' })} alt={userData.nickname} />
</span>
</RightMenu>
워크스페이스 중첩 라우터
workspace에서 채널 index를 받을때
1. children 주소 받기
//channel index.tsx
import Workspace from '@layouts/Workspace';
<Workspace>
<div></div>
</Workspace>
//Workspace index.tsx
{children}
2. 스위치 라우터 안에서 스위치라우트를 또사용, 주소 구조는 계층적 주고로 동일해야함! APP의 workspace 주소와 동일
//Workspace index.tsx
<Chats>
<Switch>
<Route path="/workspace/:workspace/channel/:channel" component={Channel} />
<Route path="/workspace/:workspace/dm/:id" component={DirectMessage} />
</Switch>
</Chats>
채널 만들기
채널, DM -> 소켓 IU, 실시간채팅
팝업모달을 먼저 만들겠다
onClikck, useCallback토글 이용, 프로필 다시 한번 누르면 꺼지도록 하는 함수 {showUserMenu &&
재사용 하느냐 마느냐가, 컴포넌트를 만드느냐 마느냐를 결정함. 단일책임원칙에 따라 분리하는 것 권장
<TS> props 대한 Type를 적어줘야함.
interface Props {
show: boolean;
onCloseModal: (e: any) => void;
style: CSSProperties;
closeButton?: boolean;
}
const Menu: FC<Props> = ({ children, style, show, onCloseModal, closeButton }) => {
const stopPropagation = useCallback((e) => {
e.stopPropagation();
}, []);
Stop Propagation
메뉴팝업 밖을 클릭할때 닫히게 설정, 메뉴내부 눌렀을때 팝업 닫히면 안됨.
부모div 선택시 닫히고, 본인영역 선택시 stopPropagation
==html 이벤트 버블링
<CreateMenu onClick={onCloseModal}> //부모 모달닫는것
<div style={style} onClick={stopPropagation}> //본인 닫는것 stop 멈춤
{closeButton && <CloseModalButton onClick={onCloseModal}>×</CloseModalButton>}
{children}
</div>
</CreateMenu>
props 기본값 설정
Menu.defaultProps = {
closeButton: true,
};
프로필 팝업 전체코드
<span onClick={onClickUserProfile}>
<ProfileImg src={gravatar.url(userData.email, { s: '28px', d: 'retro' })} alt={userData.nickname} />
{showUserMenu && (
<Menu style={{ right: 0, top: 38 }} show={showUserMenu} onCloseModal={onCloseUserProfile}>
<ProfileModal>
<img src={gravatar.url(userData.nickname, { s: '36px', d: 'retro' })} alt={userData.nickname} />
<div>
<span id="profile-name">{userData.nickname}</span>
<span id="profile-active">Active</span>
</div>
</ProfileModal>
<LogOutButton onClick={onLogout}>로그아웃</LogOutButton>
</Menu>
)}
</span>
모달만들기
워크스페이스 생성 모달 팝업
<AddButton onClick={onClickCreateWorkspace}>+</AddButton>
<TS> 에러 Workspace 무슨 형식인지 모른다는 에러 -> 데이터형식을 서버에서 확인
useSWR<IChannel[]>
메뉴 : 클릭한 부분 바로 밑에 뜨는 팝업
모달 : 전체 팝업창처럼 뜨는 것. 코드 유사함
input이 들어있는 부분의 경우, 별도의 컴포넌트로 빼는게 좋음. 안빼면 리렌더링이 계속 반복됨
워크스페이스 생성 시 e.preventDefault 새로고침 방지, 필수값들 들어있나 검사
(e) => {
e.preventDefault();
if (!newWorkspace || !newWorkspace.trim()) return;
if (!newUrl || !newUrl.trim()) return;
.trim 띄어쓰기 하는 것도 같이 검사! 막아줌
에러 사용자에게 띄워주는 라이브러리 npm i react-toastify
import {toast} from 'react-toastify';
toast.error(error.response?.data, { position: 'bottom-center' });
채널 만드는 모달
워크스패이스 id, 이름 생성에 input이 사용되기 때문에 컴포넌트로 빼는 과정
유지보수, 성능에 유용
Invalid Hook Hole
retrun 아래 Hooks 있을때, 반복문 안에 Hooks 넣었을때 발생하는 오류
라우터 주소 설계(라우트 파라미터)
주소칸 :workspace -> 라우트 파라미터, 사용자가 자유롭게 값을 변경할 수 있음. 기존주소와 같이 있을때는 라유트파라미터가 제일 밑에 존재해야한다
workspace, channel, id 모두 : 라우트파라미터 설정을 해주면, 각각의 실제값이 들어감
채널을 새로 만드는 axios 코드
const { workspace, channel } = useParams<{ workspace: string; channel: string }>();
...
axios.post(
`/api/workspaces/${workspace}/channels`,
{
name: newChannel,
},
{
withCredentials: true,
},
)
//workspace index.tsx
const { data: channelData } = useSWR<IChannel[]>(userData ? `/api/workspaces/${workspace}/channels` : null, fetcher);
상태관리를 주소에서 값을 받아와서 서버에 전달을 함
SWL 조건부 요청 -> 로그인 했을때와 안했을때 가져오는 데이터 값을 다르게 할 수 있음
channelData?.map(~ : 데이터가 없을수도 있는 경우 ? 적어놓음
revalidate로 fetch 요청 SWR로 데이터 관리하고 있음
mutate(~, false) revalidate 대신에 이 함수 넣으면 서버에 요청을 보내지 않음
사용자 초대 모달 만들기
채널, 워크스페이스 생성 모달과 유사!
이메일 적어서 보내기
invite workspace, channel 모달 컴포넌트로 모두 생성
느낀점
gravatar 실제로 사용할 것 같다.
화면구성은 컴포넌트화, 주소구조, 라우터를 이용해 잘 짜야한다고 깨달았다. 미리 큰그림을 그려놓는게 필수!
팝업을 배웠으니, 플젝에 실제로 적용해 보겠다!