diff --git a/frontend/src/components/Answer/AnswerIcon.tsx b/frontend/src/components/Answer/AnswerIcon.tsx index 27ee3faf6..991c79d7e 100644 --- a/frontend/src/components/Answer/AnswerIcon.tsx +++ b/frontend/src/components/Answer/AnswerIcon.tsx @@ -38,6 +38,4 @@ const AnswerIcon = ({ answer, isSelected = false, ...rest }: Props) => { ); }; -const MemoAnswerIcon = React.memo(AnswerIcon); - -export default MemoAnswerIcon; +export default React.memo(AnswerIcon); diff --git a/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestion.tsx b/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestion.tsx index b815c9003..64dbe8558 100644 --- a/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestion.tsx +++ b/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestion.tsx @@ -2,12 +2,9 @@ import styled from '@emotion/styled'; import React from 'react'; import HighlightText from '@/components/_common/Highlight/HighlightText'; -import { useTabContext } from '@/components/_common/Tabs/TabContext'; -import AnswerIcon from '@/components/Answer/AnswerIcon'; -import { ANSWER_OPTIONS } from '@/constants/answer'; -import useChecklistAnswer from '@/hooks/useChecklistAnswer'; +import ChecklistQuestionAnswers from '@/components/NewChecklist/ChecklistQuestion/ChecklistQuestionAnswers'; import { flexCenter, flexRow, flexSpaceBetween } from '@/styles/common'; -import { Answer, AnswerType } from '@/types/answer'; +import { AnswerType } from '@/types/answer'; import { ChecklistQuestion } from '@/types/checklist'; interface Props { @@ -18,31 +15,19 @@ interface Props { const ChecklistQuestionItem = ({ answer, question }: Props) => { const { questionId, title, highlights } = question; - const { updateAndToggleAnswer: updateAnswer } = useChecklistAnswer(); - const { currentTabId } = useTabContext(); - - const handleClick = (newAnswer: AnswerType) => { - updateAnswer({ categoryId: currentTabId, questionId: questionId, newAnswer }); - }; - return ( - {ANSWER_OPTIONS.map((option: Answer) => ( -
handleClick(option.name)}> - -
- ))} +
); }; -const ChecklistQuestionItemMemo = React.memo(ChecklistQuestionItem); -export default ChecklistQuestionItemMemo; +export default React.memo(ChecklistQuestionItem); const S = { Container: styled.div` diff --git a/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestionAnswers.tsx b/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestionAnswers.tsx new file mode 100644 index 000000000..3f2980ff9 --- /dev/null +++ b/frontend/src/components/NewChecklist/ChecklistQuestion/ChecklistQuestionAnswers.tsx @@ -0,0 +1,38 @@ +import React, { useCallback } from 'react'; + +import { useTabContext } from '@/components/_common/Tabs/TabContext'; +import AnswerIcon from '@/components/Answer/AnswerIcon'; +import { ANSWER_OPTIONS } from '@/constants/answer'; +import useChecklistAnswer from '@/hooks/useChecklistAnswer'; +import { Answer, AnswerType } from '@/types/answer'; + +const ChecklistQuestionAnswers = ({ answer, questionId }: { answer: AnswerType; questionId: number }) => { + const { updateAndToggleAnswer: updateAnswer } = useChecklistAnswer(); + const { currentTabId } = useTabContext(); + + const handleClick = useCallback( + (newAnswer: AnswerType) => { + updateAnswer({ categoryId: currentTabId, questionId: questionId, newAnswer }); + }, + [currentTabId, questionId, updateAnswer], + ); + + return ( + <> + {ANSWER_OPTIONS.map((option: Answer) => { + const isSelected = answer === option.name; + + return ( + handleClick(option.name)} + key={`${questionId}-${option.id}`} + /> + ); + })} + + ); +}; + +export default React.memo(ChecklistQuestionAnswers); diff --git a/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomName.tsx b/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomName.tsx index 9a0e338a6..b1604f176 100644 --- a/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomName.tsx +++ b/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomName.tsx @@ -1,6 +1,8 @@ +import React from 'react'; import { useStore } from 'zustand'; import FormField from '@/components/_common/FormField/FormField'; +import useDefaultRoomName from '@/components/NewChecklist/NewRoomInfoForm/useDefaultRoomName'; import checklistRoomInfoStore from '@/store/checklistRoomInfoStore'; const RoomName = () => { @@ -8,6 +10,8 @@ const RoomName = () => { const roomName = useStore(checklistRoomInfoStore, state => state.rawValue.roomName); const errorMessage = useStore(checklistRoomInfoStore, state => state.errorMessage.roomName); + useDefaultRoomName(); + return ( @@ -17,4 +21,4 @@ const RoomName = () => { ); }; -export default RoomName; +export default React.memo(RoomName); diff --git a/frontend/src/components/_common/Highlight/HighlightText.tsx b/frontend/src/components/_common/Highlight/HighlightText.tsx index 0009f26be..03b7b47fc 100644 --- a/frontend/src/components/_common/Highlight/HighlightText.tsx +++ b/frontend/src/components/_common/Highlight/HighlightText.tsx @@ -29,9 +29,7 @@ const HighlightText = ({ title, highlights }: Props) => { return {highlightText({ title, highlights })}; }; -const MemoHighlightText = React.memo(HighlightText); - -export default MemoHighlightText; +export default React.memo(HighlightText); const S = { Title: styled.div` diff --git a/frontend/src/components/_common/Tabs/Tab.tsx b/frontend/src/components/_common/Tabs/Tab.tsx index 054e9a3f2..83c580295 100644 --- a/frontend/src/components/_common/Tabs/Tab.tsx +++ b/frontend/src/components/_common/Tabs/Tab.tsx @@ -1,6 +1,7 @@ import '@/styles/category-sprite-image.css'; import styled from '@emotion/styled'; +import React from 'react'; import { flexCenter } from '@/styles/common'; @@ -22,7 +23,7 @@ const Tab = ({ id, onMoveTab, name, active, hasIndicator, className }: Props) => ); }; -export default Tab; +export default React.memo(Tab); const S = { Container: styled.div<{ active: boolean }>` diff --git a/frontend/src/components/_common/Tabs/Tabs.tsx b/frontend/src/components/_common/Tabs/Tabs.tsx index 23e969e72..d9d74430c 100644 --- a/frontend/src/components/_common/Tabs/Tabs.tsx +++ b/frontend/src/components/_common/Tabs/Tabs.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; +import { useCallback } from 'react'; import Tab from '@/components/_common/Tabs/Tab'; import { useTabContext } from '@/components/_common/Tabs/TabContext'; @@ -20,9 +21,12 @@ export interface TabWithCompletion extends Tab { const Tabs = ({ tabList }: Props) => { const { currentTabId, setCurrentTabId } = useTabContext(); - const onMoveTab = (tabId: number) => { - setCurrentTabId(tabId); - }; + const onMoveTab = useCallback( + (tabId: number) => { + setCurrentTabId(tabId); + }, + [setCurrentTabId], + ); return ( diff --git a/frontend/src/hooks/useChecklistAnswer.ts b/frontend/src/hooks/useChecklistAnswer.ts index e9c7ddfd3..8e0aa336f 100644 --- a/frontend/src/hooks/useChecklistAnswer.ts +++ b/frontend/src/hooks/useChecklistAnswer.ts @@ -1,3 +1,5 @@ +import { useCallback } from 'react'; + import useChecklistStore from '@/store/useChecklistStore'; import { AnswerType } from '@/types/answer'; import { CategoryAndQuestion } from '@/types/checklist'; @@ -10,42 +12,48 @@ const useChecklistAnswer = () => { const checklistActions = useChecklistStore(store => store.actions); const checklistCategoryQnA = useChecklistStore(store => store.checklistCategoryQnA); - const updateAndToggleAnswer = ({ categoryId, questionId, newAnswer }: UpdateAnswerProps) => { - const targetCategory = checklistActions.getCategory(categoryId); - - if (targetCategory) { - const updatedCategory = { - ...targetCategory, - questions: targetCategory.questions.map(question => { - if (question.questionId === questionId) { - return { ...question, answer: question.answer === newAnswer ? 'NONE' : newAnswer }; - } - return question; - }), - }; - - const newCategories = checklistCategoryQnA.map(category => - category.categoryId === categoryId ? updatedCategory : category, - ); - - checklistActions.set(newCategories); - } - }; - - const findCategoryQuestion = ({ categoryId, questionId }: CategoryAndQuestion) => { - const targetCategory = checklistCategoryQnA?.find(category => category.categoryId === categoryId); - - if (!targetCategory) { - throw new Error(`${categoryId}가 아이디인 카테고리를 찾을 수 없습니다.`); - } - - const targetQuestion = targetCategory.questions.find(q => q.questionId === questionId); - if (!targetQuestion) { - throw new Error(`${categoryId}가 아이디인 카테고리 내에서 ${questionId}가 아이디인 질문을 찾을 수 없습니다.`); - } - - return targetQuestion; - }; + const updateAndToggleAnswer = useCallback( + ({ categoryId, questionId, newAnswer }: UpdateAnswerProps) => { + const targetCategory = checklistActions.getCategory(categoryId); + + if (targetCategory) { + const updatedCategory = { + ...targetCategory, + questions: targetCategory.questions.map(question => { + if (question.questionId === questionId) { + return { ...question, answer: question.answer === newAnswer ? 'NONE' : newAnswer }; + } + return question; + }), + }; + + const newCategories = checklistCategoryQnA.map(category => + category.categoryId === categoryId ? updatedCategory : category, + ); + + checklistActions.set(newCategories); + } + }, + [checklistActions, checklistCategoryQnA], + ); + + const findCategoryQuestion = useCallback( + ({ categoryId, questionId }: CategoryAndQuestion) => { + const targetCategory = checklistCategoryQnA?.find(category => category.categoryId === categoryId); + + if (!targetCategory) { + throw new Error(`${categoryId}가 아이디인 카테고리를 찾을 수 없습니다.`); + } + + const targetQuestion = targetCategory.questions.find(q => q.questionId === questionId); + if (!targetQuestion) { + throw new Error(`${categoryId}가 아이디인 카테고리 내에서 ${questionId}가 아이디인 질문을 찾을 수 없습니다.`); + } + + return targetQuestion; + }, + [checklistCategoryQnA], + ); return { updateAndToggleAnswer, findCategoryQuestion }; }; diff --git a/frontend/src/hooks/useInitialChecklist.ts b/frontend/src/hooks/useInitialChecklist.ts index 873e84cbc..e2e06f034 100644 --- a/frontend/src/hooks/useInitialChecklist.ts +++ b/frontend/src/hooks/useInitialChecklist.ts @@ -1,14 +1,11 @@ import { useEffect } from 'react'; -import useDefaultRoomName from '@/components/NewChecklist/NewRoomInfoForm/useDefaultRoomName'; import useGetChecklistQuestionQuery from '@/hooks/query/useGetChecklistQuestionQuery'; import useChecklistStore from '@/store/useChecklistStore'; const useInitialChecklist = () => { const initAnswerSheetIfEmpty = useChecklistStore(state => state.actions.initAnswerSheetIfEmpty); - useDefaultRoomName(); - const result = useGetChecklistQuestionQuery(); useEffect(() => {