From 6edaafca6db4f1d2d33aa02ead66d5d6d303b3b2 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Fri, 8 Sep 2023 16:25:47 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20(#530)=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=A7=84=EC=9E=85=20=EC=8B=9C=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?,=20=EC=B5=9C=EC=8B=A0=EC=88=9C=EC=9C=BC=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=98=B5=EC=85=98=EC=9D=B4=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/context/postOption.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/hooks/context/postOption.tsx b/frontend/src/hooks/context/postOption.tsx index 650a2eb6e..806f72255 100644 --- a/frontend/src/hooks/context/postOption.tsx +++ b/frontend/src/hooks/context/postOption.tsx @@ -21,7 +21,7 @@ interface PostOptionContextProps { export default function PostOptionProvider({ children }: PropsWithChildren) { const [postOption, setPostOption] = useState({ sorting: SORTING.LATEST, - status: STATUS.PROGRESS, + status: STATUS.ALL, }); return ( From 6d8bc376f15d20a21b4da551cc125731a7faec00 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Fri, 8 Sep 2023 17:26:07 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20(#534)=20=EC=95=84=EB=AC=B4?= =?UTF-8?q?=EB=9F=B0=20=EA=B0=92=EC=9D=B4=20=EC=97=86=EC=9C=BC=EB=A9=B4=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84=20=EC=95=9E=20=EB=92=A4?= =?UTF-8?q?=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20=EC=A0=9C=EA=B1=B0=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EA=B2=80=EC=83=89=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EA=B2=80=EC=83=89=EC=96=B4=EB=A5=BC=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=B0=BD=EC=97=90=20=EB=82=A8=EA=B8=B0?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/__test__/hooks/useSearch.test.tsx | 41 +++++++++++++++++++ frontend/__test__/hooks/useSelect.test.tsx | 2 +- .../src/components/common/SearchBar/index.tsx | 10 ++++- frontend/src/hooks/useCurrentKeyword.ts | 12 ++++++ frontend/src/hooks/usePostRequestInfo.ts | 20 ++++----- frontend/src/hooks/useSearch.ts | 33 +++++++++++++++ 6 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 frontend/__test__/hooks/useSearch.test.tsx create mode 100644 frontend/src/hooks/useCurrentKeyword.ts create mode 100644 frontend/src/hooks/useSearch.ts diff --git a/frontend/__test__/hooks/useSearch.test.tsx b/frontend/__test__/hooks/useSearch.test.tsx new file mode 100644 index 000000000..5206848bd --- /dev/null +++ b/frontend/__test__/hooks/useSearch.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; + +import { fireEvent, render, renderHook, screen } from '@testing-library/react'; + +import { useSearch } from '@hooks/useSearch'; + +describe('useSearch 훅이 검색을 하는지 확인한다.', () => { + test('초기 값이 없다면 keyword는 빈 문자열이다.', () => { + const { result } = renderHook(() => useSearch(), { wrapper: MemoryRouter }); + + const { keyword } = result.current; + + expect(keyword).toBe(''); + }); + + test('초기 값이 있다면 keyword 값에 설정된다.', () => { + const KEYWORD = '갤럭시'; + + const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter }); + + const { keyword } = result.current; + + expect(keyword).toBe(KEYWORD); + }); + + test('onChange 이벤트를 한다면 keyword 값에 설정된다.', () => { + const KEYWORD = '갤럭시'; + + const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter }); + const { keyword, onKeywordChange } = result.current; + + render(); + + const input = screen.getByLabelText('search-input'); + + fireEvent.change(input, { target: { value: KEYWORD } }); + + expect(result.current.keyword).toBe(KEYWORD); + }); +}); diff --git a/frontend/__test__/hooks/useSelect.test.tsx b/frontend/__test__/hooks/useSelect.test.tsx index 962cafa1e..8891cf209 100644 --- a/frontend/__test__/hooks/useSelect.test.tsx +++ b/frontend/__test__/hooks/useSelect.test.tsx @@ -8,7 +8,7 @@ import { PostStatus } from '@components/post/PostListPage/types'; const INIT_SELECTED_OPTION = 'progress'; const CHANGE_SELECTED_OPTION = 'closed'; -describe('usePostList 훅이 전체 게시글 목록을 불러오는지 확인한다', () => { +describe('useSelect 훅이 전체 게시글 목록을 불러오는지 확인한다', () => { test('초기 값이 설정 되었는지 확인한다.', () => { const { result } = renderHook(() => useSelect(INIT_SELECTED_OPTION)); diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 9000770ad..807b5f984 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -2,6 +2,9 @@ import { HTMLAttributes } from 'react'; import { Size } from '@type/style'; +import { useCurrentKeyword } from '@hooks/useCurrentKeyword'; +import { useSearch } from '@hooks/useSearch'; + import { PATH } from '@constants/path'; import { SEARCH_KEYWORD } from '@constants/post'; @@ -15,11 +18,16 @@ interface SearchBarProps extends HTMLAttributes { } export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { + const { currentKeyword } = useCurrentKeyword(); + const { keyword, onKeywordChange, onSearchSubmit, searchInputRef } = useSearch(currentKeyword); return ( - + diff --git a/frontend/src/hooks/useCurrentKeyword.ts b/frontend/src/hooks/useCurrentKeyword.ts new file mode 100644 index 000000000..17659fc96 --- /dev/null +++ b/frontend/src/hooks/useCurrentKeyword.ts @@ -0,0 +1,12 @@ +import { useSearchParams } from 'react-router-dom'; + +import { DEFAULT_KEYWORD, SEARCH_KEYWORD, SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post'; + +export const useCurrentKeyword = () => { + const [searchParams] = useSearchParams(); + const currentKeyword = + searchParams.get(SEARCH_KEYWORD)?.toString().slice(0, SEARCH_KEYWORD_MAX_LENGTH) ?? + DEFAULT_KEYWORD; + + return { currentKeyword }; +}; diff --git a/frontend/src/hooks/usePostRequestInfo.ts b/frontend/src/hooks/usePostRequestInfo.ts index 92312e15d..83df6707e 100644 --- a/frontend/src/hooks/usePostRequestInfo.ts +++ b/frontend/src/hooks/usePostRequestInfo.ts @@ -1,18 +1,14 @@ -import { useLocation, useParams, useSearchParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import { PostRequestKind } from '@components/post/PostListPage/types'; import { PATH } from '@constants/path'; -import { - DEFAULT_CATEGORY_ID, - DEFAULT_KEYWORD, - POST_TYPE, - SEARCH_KEYWORD, - SEARCH_KEYWORD_MAX_LENGTH, -} from '@constants/post'; +import { DEFAULT_CATEGORY_ID, POST_TYPE } from '@constants/post'; import { getPathFragment } from '@utils/getPathFragment'; +import { useCurrentKeyword } from './useCurrentKeyword'; + const REQUEST_URL: Record = { [PATH.HOME]: POST_TYPE.ALL, [PATH.POST_CATEGORY]: POST_TYPE.CATEGORY, @@ -23,19 +19,17 @@ const REQUEST_URL: Record = { export const usePostRequestInfo = () => { const params = useParams<{ categoryId?: string }>(); - const [searchParams] = useSearchParams(); + const { currentKeyword } = useCurrentKeyword(); const { pathname } = useLocation(); const categoryId = Number(params.categoryId ?? DEFAULT_CATEGORY_ID); - const keyword = - searchParams.get(SEARCH_KEYWORD)?.toString().slice(0, SEARCH_KEYWORD_MAX_LENGTH) ?? - DEFAULT_KEYWORD; + const convertedPathname = getPathFragment(pathname); const postType = REQUEST_URL[convertedPathname]; const postOptionalOption = { categoryId, - keyword, + keyword: currentKeyword, }; if (!postType) { diff --git a/frontend/src/hooks/useSearch.ts b/frontend/src/hooks/useSearch.ts new file mode 100644 index 000000000..db23388f3 --- /dev/null +++ b/frontend/src/hooks/useSearch.ts @@ -0,0 +1,33 @@ +import { ChangeEvent, FormEvent, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +export const useSearch = (initialKeyword = '') => { + const navigate = useNavigate(); + const searchInputRef = useRef(null); + const [keyword, setKeyword] = useState(initialKeyword); + + const onKeywordChange = (event: ChangeEvent) => { + if (!searchInputRef.current) return; + + setKeyword(event.currentTarget.value); + searchInputRef.current.setCustomValidity(''); + }; + + const onSearchSubmit = (event: FormEvent) => { + event.preventDefault(); + + if (!searchInputRef.current) return; + + const trimmedKeyword = keyword.trim(); + + if (trimmedKeyword === '') { + searchInputRef.current.setCustomValidity('검색어를 입력해주세요'); + searchInputRef.current.reportValidity(); + return; + } + + navigate(`/search?keyword=${trimmedKeyword}`); + }; + + return { keyword, onKeywordChange, onSearchSubmit, searchInputRef }; +}; From 12bedcde6bbe969e568f917f6004429df2e683e4 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Fri, 8 Sep 2023 17:33:10 +0900 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20(#530)=20=EC=99=BC=EC=AA=BD=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=83=AD=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B2=80=EC=83=89=EC=96=B4=EB=A5=BC=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EB=8D=98=20=EA=B8=B0=EB=8A=A5=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/__test__/getSelectedState.test.ts | 46 ------------------- .../Dashboard/CategorySection/index.tsx | 3 +- frontend/src/utils/post/getSelectedState.ts | 16 +------ 3 files changed, 2 insertions(+), 63 deletions(-) diff --git a/frontend/__test__/getSelectedState.test.ts b/frontend/__test__/getSelectedState.test.ts index cf9f6fc51..2f164685f 100644 --- a/frontend/__test__/getSelectedState.test.ts +++ b/frontend/__test__/getSelectedState.test.ts @@ -8,7 +8,6 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글 const state: SelectedState = { postType: 'category', categoryId, - keyword: '', categoryList: MOCK_CATEGORY_LIST, }; @@ -18,53 +17,10 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글 expect(result).toBe(MOCK_CATEGORY_LIST[0].name); }); - test('현재 검색을 한 상태이면, 검색 키워드를 반환한다.', () => { - const keyword = '갤럭시'; - const state: SelectedState = { - postType: 'search', - categoryId: 0, - keyword, - categoryList: MOCK_CATEGORY_LIST, - }; - - const result = getSelectedState(state); - - expect(result).toBe(keyword); - }); - - test('검색어를 길게 설정한 경우 10자만 보여주고 ...으로 표시해서 보여준다.', () => { - const keyword = '아이폰갤럭시뉴진스아이브세븐틴슈퍼주니어임'; - const state: SelectedState = { - postType: 'search', - categoryId: 0, - keyword, - categoryList: MOCK_CATEGORY_LIST, - }; - - const result = getSelectedState(state); - - expect(result).toBe('아이폰갤럭시뉴진스아...'); - }); - - test('검색어를 10글자인 경우 10글자 전부 표시해서 보여준다.', () => { - const keyword = '아이폰갤럭시뉴진스아'; - const state: SelectedState = { - postType: 'search', - categoryId: 0, - keyword, - categoryList: MOCK_CATEGORY_LIST, - }; - - const result = getSelectedState(state); - - expect(result).toBe('아이폰갤럭시뉴진스아'); - }); - test('현재 홈 화면에 있다면, "전체"를 반환한다', () => { const state: SelectedState = { postType: 'posts', categoryId: 0, - keyword: '', categoryList: MOCK_CATEGORY_LIST, }; @@ -77,7 +33,6 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글 const state: SelectedState = { postType: 'myPost', categoryId: 0, - keyword: '', categoryList: MOCK_CATEGORY_LIST, }; @@ -90,7 +45,6 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글 const state: SelectedState = { postType: 'myVote', categoryId: 0, - keyword: '', categoryList: MOCK_CATEGORY_LIST, }; diff --git a/frontend/src/components/common/Dashboard/CategorySection/index.tsx b/frontend/src/components/common/Dashboard/CategorySection/index.tsx index bc09a840d..0c0af29db 100644 --- a/frontend/src/components/common/Dashboard/CategorySection/index.tsx +++ b/frontend/src/components/common/Dashboard/CategorySection/index.tsx @@ -19,11 +19,10 @@ export default function CategorySection() { const categoryListFallback = categoryList ?? []; const { postOptionalOption, postType } = usePostRequestInfo(); - const { categoryId, keyword } = postOptionalOption; + const { categoryId } = postOptionalOption; const selectedState = getSelectedState({ categoryId, - keyword, categoryList: categoryListFallback, postType, }); diff --git a/frontend/src/utils/post/getSelectedState.ts b/frontend/src/utils/post/getSelectedState.ts index ae7582784..dae930607 100644 --- a/frontend/src/utils/post/getSelectedState.ts +++ b/frontend/src/utils/post/getSelectedState.ts @@ -5,30 +5,16 @@ import { PostRequestKind } from '@components/post/PostListPage/types'; export interface SelectedState { postType: PostRequestKind; categoryId: number; - keyword: string; categoryList: Category[]; } -const SLICED_LENGTH_NUMBER = 10; - -export const getSelectedState = ({ - postType, - categoryId, - keyword, - categoryList, -}: SelectedState) => { +export const getSelectedState = ({ postType, categoryId, categoryList }: SelectedState) => { if (postType === 'category') { const selectedCategory = categoryList.find(category => category.id === categoryId); return selectedCategory?.name ?? '전체'; } - if (postType === 'search') { - return keyword.length > SLICED_LENGTH_NUMBER - ? `${keyword.slice(0, SLICED_LENGTH_NUMBER)}...` - : keyword; - } - if (postType === 'myPost') { return '내가 작성한 글'; } From b5fc22b0215d5fbd3c8abf757cf2324b4bce2a00 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Fri, 8 Sep 2023 17:52:45 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20(#530)=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=96=B4=EC=99=80=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=ED=95=9C=20=EA=B2=80=EC=83=89=EC=96=B4=EA=B0=80=20?= =?UTF-8?q?=EB=8B=A4=EB=A5=BC=20=EB=95=8C=20=EA=B3=B5=EB=B0=B1=EC=9D=84=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=ED=95=9C=20=EA=B2=80=EC=83=89=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=20state=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useSearch.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/hooks/useSearch.ts b/frontend/src/hooks/useSearch.ts index db23388f3..4ac6ae402 100644 --- a/frontend/src/hooks/useSearch.ts +++ b/frontend/src/hooks/useSearch.ts @@ -26,6 +26,10 @@ export const useSearch = (initialKeyword = '') => { return; } + if (keyword !== trimmedKeyword) { + setKeyword(trimmedKeyword); + } + navigate(`/search?keyword=${trimmedKeyword}`); }; From e098e4365bf507b856d79652b4b1a79ff4cfd884 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Fri, 8 Sep 2023 18:03:15 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20(#530)=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=96=B4=20=EC=9E=90=EB=8F=99=EC=99=84=EC=84=B1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/SearchBar/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 807b5f984..587bfa3b9 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -28,6 +28,7 @@ export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { type="search" value={keyword} onChange={onKeywordChange} + autoComplete="off" name={SEARCH_KEYWORD} {...rest} /> From 696b507f9233592ef95046573b0e9db03d659bd4 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Mon, 11 Sep 2023 15:50:59 +0900 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20(#530)=20onKeywordChange=EC=97=90?= =?UTF-8?q?=EC=84=9C=20handleKeywordChange=EB=A1=9C=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/__test__/hooks/useSearch.test.tsx | 4 ++-- frontend/src/components/common/SearchBar/index.tsx | 5 +++-- frontend/src/hooks/useSearch.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/__test__/hooks/useSearch.test.tsx b/frontend/__test__/hooks/useSearch.test.tsx index 5206848bd..b1691f3b3 100644 --- a/frontend/__test__/hooks/useSearch.test.tsx +++ b/frontend/__test__/hooks/useSearch.test.tsx @@ -28,9 +28,9 @@ describe('useSearch 훅이 검색을 하는지 확인한다.', () => { const KEYWORD = '갤럭시'; const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter }); - const { keyword, onKeywordChange } = result.current; + const { keyword, handleKeywordChange } = result.current; - render(); + render(); const input = screen.getByLabelText('search-input'); diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 587bfa3b9..8dd68c2fe 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -19,7 +19,8 @@ interface SearchBarProps extends HTMLAttributes { export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { const { currentKeyword } = useCurrentKeyword(); - const { keyword, onKeywordChange, onSearchSubmit, searchInputRef } = useSearch(currentKeyword); + const { keyword, handleKeywordChange, onSearchSubmit, searchInputRef } = + useSearch(currentKeyword); return ( { const searchInputRef = useRef(null); const [keyword, setKeyword] = useState(initialKeyword); - const onKeywordChange = (event: ChangeEvent) => { + const handleKeywordChange = (event: ChangeEvent) => { if (!searchInputRef.current) return; setKeyword(event.currentTarget.value); @@ -33,5 +33,5 @@ export const useSearch = (initialKeyword = '') => { navigate(`/search?keyword=${trimmedKeyword}`); }; - return { keyword, onKeywordChange, onSearchSubmit, searchInputRef }; + return { keyword, handleKeywordChange, onSearchSubmit, searchInputRef }; }; From be04d6b7aa0d7febcb08fd0acb250687ee6fb1d4 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Mon, 11 Sep 2023 16:02:39 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20(#530)=20onSearchSubmit=EC=97=90?= =?UTF-8?q?=EC=84=9C=20handleSearchSubmit=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/SearchBar/index.tsx | 4 ++-- frontend/src/hooks/useSearch.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 8dd68c2fe..6af47280d 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -19,10 +19,10 @@ interface SearchBarProps extends HTMLAttributes { export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { const { currentKeyword } = useCurrentKeyword(); - const { keyword, handleKeywordChange, onSearchSubmit, searchInputRef } = + const { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef } = useSearch(currentKeyword); return ( - + { searchInputRef.current.setCustomValidity(''); }; - const onSearchSubmit = (event: FormEvent) => { + const handleSearchSubmit = (event: FormEvent) => { event.preventDefault(); if (!searchInputRef.current) return; @@ -33,5 +33,5 @@ export const useSearch = (initialKeyword = '') => { navigate(`/search?keyword=${trimmedKeyword}`); }; - return { keyword, handleKeywordChange, onSearchSubmit, searchInputRef }; + return { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef }; }; From 9e57d99f338a6c7bf9a8976d982279cea6a02829 Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Mon, 11 Sep 2023 16:24:27 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20(#530)=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=96=B4=EC=97=90=EC=84=9C=20=EC=A4=91=EA=B0=84=EC=9D=98=20?= =?UTF-8?q?=EA=B3=B5=EB=B0=B1=EC=9D=B4=20=EB=A7=8E=EC=9D=80=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EB=8F=84=20=EA=B3=A0=EB=A0=A4=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=A4=91=EA=B0=84=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/__test__/getTrimmedWord.test.ts | 15 +++++++++++++++ .../src/components/common/SearchBar/index.tsx | 1 + frontend/src/hooks/useCurrentKeyword.ts | 7 ++++++- frontend/src/hooks/useSearch.ts | 12 +++++++----- frontend/src/utils/getTrimmedWord.ts | 7 +++++++ 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 frontend/__test__/getTrimmedWord.test.ts create mode 100644 frontend/src/utils/getTrimmedWord.ts diff --git a/frontend/__test__/getTrimmedWord.test.ts b/frontend/__test__/getTrimmedWord.test.ts new file mode 100644 index 000000000..0d6092e60 --- /dev/null +++ b/frontend/__test__/getTrimmedWord.test.ts @@ -0,0 +1,15 @@ +import { getTrimmedWord } from '@utils/getTrimmedWord'; + +test.each([ + ['검색어 입니다', '검색어 입니다'], + [' 완전히 갤럭시 임 ', '완전히 갤럭시 임'], + [' ', ''], + ['', ''], +])( + 'getTrimmedWord 함수에서 단어를 입력했을 때 중복된 공백을 제거한 단어를 반환한다.', + (word, expectedWord) => { + const result = getTrimmedWord(word); + + expect(result).toBe(expectedWord); + } +); diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 6af47280d..45ace74e8 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -21,6 +21,7 @@ export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { const { currentKeyword } = useCurrentKeyword(); const { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef } = useSearch(currentKeyword); + return ( { const [searchParams] = useSearchParams(); const currentKeyword = searchParams.get(SEARCH_KEYWORD)?.toString().slice(0, SEARCH_KEYWORD_MAX_LENGTH) ?? DEFAULT_KEYWORD; - return { currentKeyword }; + return { + currentKeyword: + currentKeyword !== DEFAULT_KEYWORD ? getTrimmedWord(currentKeyword) : currentKeyword, + }; }; diff --git a/frontend/src/hooks/useSearch.ts b/frontend/src/hooks/useSearch.ts index 404faea4a..36938154d 100644 --- a/frontend/src/hooks/useSearch.ts +++ b/frontend/src/hooks/useSearch.ts @@ -1,6 +1,8 @@ import { ChangeEvent, FormEvent, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { getTrimmedWord } from '@utils/getTrimmedWord'; + export const useSearch = (initialKeyword = '') => { const navigate = useNavigate(); const searchInputRef = useRef(null); @@ -18,7 +20,11 @@ export const useSearch = (initialKeyword = '') => { if (!searchInputRef.current) return; - const trimmedKeyword = keyword.trim(); + const trimmedKeyword = getTrimmedWord(keyword); + + if (keyword !== trimmedKeyword) { + setKeyword(trimmedKeyword); + } if (trimmedKeyword === '') { searchInputRef.current.setCustomValidity('검색어를 입력해주세요'); @@ -26,10 +32,6 @@ export const useSearch = (initialKeyword = '') => { return; } - if (keyword !== trimmedKeyword) { - setKeyword(trimmedKeyword); - } - navigate(`/search?keyword=${trimmedKeyword}`); }; diff --git a/frontend/src/utils/getTrimmedWord.ts b/frontend/src/utils/getTrimmedWord.ts new file mode 100644 index 000000000..94b159c73 --- /dev/null +++ b/frontend/src/utils/getTrimmedWord.ts @@ -0,0 +1,7 @@ +export const getTrimmedWord = (word: string) => { + return word + .split(' ') + .map(word => word.trim()) + .filter(word => word !== '') + .join(' '); +}; From 106857e62586de1f47d73cda6a8d9b1f48c24dbd Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Mon, 11 Sep 2023 16:37:50 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20(#530)=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=96=B4=20100=EA=B8=80=EC=9E=90=EA=B9=8C=EC=A7=80=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/SearchBar/index.tsx | 3 ++- frontend/src/hooks/useSearch.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 45ace74e8..94e7b2296 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -6,7 +6,7 @@ import { useCurrentKeyword } from '@hooks/useCurrentKeyword'; import { useSearch } from '@hooks/useSearch'; import { PATH } from '@constants/path'; -import { SEARCH_KEYWORD } from '@constants/post'; +import { SEARCH_KEYWORD, SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post'; import searchIcon from '@assets/search_black.svg'; @@ -26,6 +26,7 @@ export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { { @@ -11,6 +13,15 @@ export const useSearch = (initialKeyword = '') => { const handleKeywordChange = (event: ChangeEvent) => { if (!searchInputRef.current) return; + if (event.currentTarget.value.length > SEARCH_KEYWORD_MAX_LENGTH) { + searchInputRef.current.setCustomValidity( + `검색어는 ${SEARCH_KEYWORD_MAX_LENGTH}자까지 입력 가능합니다.` + ); + searchInputRef.current.reportValidity(); + + return; + } + setKeyword(event.currentTarget.value); searchInputRef.current.setCustomValidity(''); }; From faed463ca4934d46630f4240f9cade959ce399ef Mon Sep 17 00:00:00 2001 From: Gilpop8663 Date: Mon, 11 Sep 2023 16:48:41 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20(#530)=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=20hook=EC=9D=84=20=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=B4=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/SearchBar/index.tsx | 2 +- frontend/src/hooks/useSearch.ts | 18 +++++------------- frontend/src/hooks/useText.ts | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx index 94e7b2296..17e9861ff 100644 --- a/frontend/src/components/common/SearchBar/index.tsx +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -26,7 +26,7 @@ export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { { const navigate = useNavigate(); const searchInputRef = useRef(null); - const [keyword, setKeyword] = useState(initialKeyword); + const { text: keyword, setText: setKeyword, handleTextChange } = useText(initialKeyword); const handleKeywordChange = (event: ChangeEvent) => { if (!searchInputRef.current) return; - if (event.currentTarget.value.length > SEARCH_KEYWORD_MAX_LENGTH) { - searchInputRef.current.setCustomValidity( - `검색어는 ${SEARCH_KEYWORD_MAX_LENGTH}자까지 입력 가능합니다.` - ); - searchInputRef.current.reportValidity(); - - return; - } - - setKeyword(event.currentTarget.value); - searchInputRef.current.setCustomValidity(''); + handleTextChange(event, { MAX_LENGTH: SEARCH_KEYWORD_MAX_LENGTH, MIN_LENGTH: 0 }); }; const handleSearchSubmit = (event: FormEvent) => { diff --git a/frontend/src/hooks/useText.ts b/frontend/src/hooks/useText.ts index 7faca87ca..6ff6ce6c6 100644 --- a/frontend/src/hooks/useText.ts +++ b/frontend/src/hooks/useText.ts @@ -26,5 +26,5 @@ export const useText = (originalText: string) => { setText(''); }; - return { text, handleTextChange, resetText }; + return { text, setText, handleTextChange, resetText }; };