Skip to content

Commit

Permalink
Fix: 팔로우 모달, 다이렉트 메세지 모달 타이핑 시 스켈레톤 자연스럽게 유지 (#97)
Browse files Browse the repository at this point in the history
* Refactor: UserCard 글자 색상 수정 및 주석 삭제

* Fix: UserCard 클릭 시 가끔 씹히던 현상 해결

* Feat: 본인 헤더에도 스켈레톤 적용

* Refactor: isClickedUserCard 전역 상태로 관리

* Refactor: 메세지 관련 react-query 커스텀 훅 refetchInterval 설정 및 리팩토링

* Refactor: 코드 정리 및 배포 전 QA 수정사항 반영

- isFetchedAfterMount 옵션 이용하여 처음에만 스켈레톤 길게 적용, 이후 짧게 적용
- 실시간 대화가 가능하도록 refetchInterval 적용
- 채팅방이 켜져있고 message가 refetch되어 변경되는 경우 알림을 읽도록 적용
- 글쓰기 버튼 hover시 배경 생기는 현상 삭제
- isClickedUserCard 전역 스토어로 분리
- 그 외 자식에게 넘겨준 Props들 전역 스토어에서 바로 사용하는 방식으로 변경

* Feat: useIsTyping 커스텀 훅 분리

* Refactor: 기존에 타자치던 도중에 끊기던 스켈레톤을 자연스럽게 변경, 메세지 모달에서 유저 검색 시 Admin 안나오도록 필터 추가

* Feat: 로그아웃 시 MessageReceiver 관련 스토어 전부 날리기
  • Loading branch information
kim-hyunjoo committed Jan 17, 2024
1 parent c79eed8 commit 08ab188
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 37 deletions.
5 changes: 5 additions & 0 deletions src/Components/Common/Header/HeaderTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import useAuthUserStore from '@/Stores/AuthUser';
import Badge from '@/Components/Base/Badge';
import filterNotificationLength from './filterNotificationLength';
import DropDownOnlyOption from '../../DropDownOnlyOption';
import useMessageReceiver from '@/Stores/MessageReceiver';

const HeaderTab = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -86,6 +87,8 @@ const HeaderTab = () => {

const { setAuthUser } = useAuthUserStore();

const { setReceiver, setIsClickedUserCard } = useMessageReceiver();

// useMutation으로 로그아웃 처리
const { mutate } = useMutation({
mutationFn: logout,
Expand All @@ -96,6 +99,8 @@ const HeaderTab = () => {
navigate('/');
setTab('home');
setAuthUser({});
setReceiver(null);
setIsClickedUserCard(false);
},
});

Expand Down
12 changes: 5 additions & 7 deletions src/Components/DirectMessage/MessageModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-underscore-dangle */
import { useRef, useState } from 'react';
import { useState } from 'react';
import { useTheme } from 'styled-components';
import Modal from '@/Components/Common/Modal';
import { StyledBody, StyledContainer, StyledHeader } from './style';
Expand All @@ -12,16 +12,16 @@ import DirectMessageSkeleton from '../Skeleton';
import { useSearchUsers } from '@/Hooks/Api/Search';
import useDebouncedSearch from '@/Hooks/useDebouncedSearch';
import useMessageReceiver from '@/Stores/MessageReceiver';
import useIsTyping from '@/Hooks/useIsTyping';

const MessageModal = ({
setIsModalOpen,
loginUser,
isMobileSize = false,
}: MessageModalProps) => {
const { colors } = useTheme();
const inputRef = useRef<HTMLInputElement | null>(null);
const { inputRef, isTyping } = useIsTyping();

const [isTyping, setIsTyping] = useState(false);
const [selected, setSelected] = useState<UserType | null>(null);
const [searchQuery, setSearchQuery] = useState('');

Expand All @@ -36,16 +36,14 @@ const MessageModal = ({
const debouncedSearch = useDebouncedSearch({
inputRef,
callback: setSearchQuery,
setIsTyping,
});

const handleInputChange = async () => {
const handleInputChange = () => {
setSelected(null);
setIsTyping(true);
debouncedSearch();
};

const handleClickButton = async () => {
const handleClickButton = () => {
if (!selected) {
return;
}
Expand Down
18 changes: 8 additions & 10 deletions src/Components/FollowModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable no-underscore-dangle */
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Input from '../Base/Input';
import Modal from '../Common/Modal';
Expand All @@ -14,6 +14,7 @@ import { getUser } from '@/Services/User';
import { sendNotifications } from '@/Services/Notification';
import useResize from '@/Hooks/useResize';
import useDebouncedSearch from '@/Hooks/useDebouncedSearch';
import useIsTyping from '@/Hooks/useIsTyping';

/**
* @param userData 해당 유저의 UserType 데이터
Expand All @@ -27,12 +28,13 @@ const FollowModal = ({
onChangeOpen,
}: FollowModalProps) => {
const navigator = useNavigate();
const inputRef = useRef<HTMLInputElement | null>(null);

const [isLoading, setIsLoading] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [follows, setFollows] = useState<UserType[]>([]);
const [searchFollows, setSearchFollows] = useState<UserType[]>(follows);

const { isMobileSize } = useResize();
const { inputRef, isTyping } = useIsTyping();

const search = (query: string, fetchedFollows: UserType[]) => {
// 검색 중인 단어가 없다면 전체 팔로우 목록을 보여준다.
Expand All @@ -51,7 +53,6 @@ const FollowModal = ({
inputRef,
follows,
callback: search,
setIsTyping,
});

const fetchFollowData = useCallback(async () => {
Expand Down Expand Up @@ -85,17 +86,14 @@ const FollowModal = ({
}
search(inputRef.current.value, fetchedFollows);

setTimeout(() => {
setIsLoading(false);
}, 100);
}, [mode, userData]);
setIsLoading(false);
}, [mode, userData, inputRef]);

useEffect(() => {
fetchFollowData();
}, [userData, mode, fetchFollowData]);

const handleInputChange = () => {
setIsTyping(true);
debouncedSearch();
};

Expand Down Expand Up @@ -144,7 +142,7 @@ const FollowModal = ({
return (
<Modal
height={60}
width={isMobileSize ? 80 : 40}
width={isMobileSize ? 80 : 50}
onChangeOpen={onChangeOpen}
>
<StyledContainer>
Expand Down
6 changes: 5 additions & 1 deletion src/Hooks/Api/Search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export const useSearchUsers = (query: string, myId: string) => {
return []; // query가 비어있는 경우 빈 배열 반환
}
const users = await searchUsers(query);
return users ? users.filter((user) => user._id !== myId) : [];
return users
? users.filter(
(user) => user._id !== myId && user.role !== 'SuperAdmin',
)
: [];
},
});

Expand Down
25 changes: 7 additions & 18 deletions src/Hooks/useDebouncedSearch/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { debounce } from 'lodash';
import { useMemo } from 'react';
import { Props } from './type';

const useDebouncedSearch = ({
inputRef,
follows = [],
callback,
setIsTyping,
delay = 200,
}: Props) => {
const debouncedSearch = useMemo(
() =>
debounce(() => {
if (!inputRef || !inputRef.current) {
return;
}
const query = inputRef.current.value.trim();
callback(query, follows);

setTimeout(() => {
setIsTyping(false);
}, 400);
}, delay),
[],
);

const debouncedSearch = debounce(() => {
if (!inputRef || !inputRef.current) {
return;
}
const query = inputRef.current.value.trim();
callback(query, follows);
}, delay);
return debouncedSearch;
};

Expand Down
1 change: 0 additions & 1 deletion src/Hooks/useDebouncedSearch/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ export interface Props {
inputRef: React.RefObject<HTMLInputElement>;
follows?: UserType[];
callback: (query: string, follows: UserType[]) => void;
setIsTyping: (state: boolean) => void;
delay?: number;
}
35 changes: 35 additions & 0 deletions src/Hooks/useIsTyping/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable consistent-return */
import { useEffect, useRef, useState } from 'react';

const useIsTyping = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [isTyping, setIsTyping] = useState<boolean>(false);

useEffect(() => {
const element = inputRef.current as HTMLInputElement | null;
if (!element) return;

let typingTimer: NodeJS.Timeout;

const handleTyping = () => {
clearTimeout(typingTimer);
setIsTyping(true);

typingTimer = setTimeout(() => {
setIsTyping(false);
}, 1000); // 타자 입력이 멈추고 1초 후에 setIsTyping(false) 호출
};

element.addEventListener('input', handleTyping);

// 컴포넌트 언마운트 시 이벤트 핸들러 제거
return () => {
element.removeEventListener('input', handleTyping);
clearTimeout(typingTimer);
};
}, []);

return { inputRef, isTyping, setIsTyping };
};

export default useIsTyping;

0 comments on commit 08ab188

Please sign in to comment.