diff --git a/frontend/src/features/liberation/pc/Liberation.jsx b/frontend/src/features/liberation/pc/Liberation.jsx index 32f28c2..f6032c4 100644 --- a/frontend/src/features/liberation/pc/Liberation.jsx +++ b/frontend/src/features/liberation/pc/Liberation.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useLayoutEffect } from 'react' +import { useState, useEffect, useLayoutEffect, useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import dayjs from 'dayjs' import { api } from '../../../api/client' @@ -261,7 +261,12 @@ export default function Liberation() { return { start: ws, end: ws.add(6, 'day') } } - const completionDate = computeCompletionDate() + const completionDate = useMemo( + () => computeCompletionDate(), + // 의도적으로 state 전체 + calcMode 만 의존. 내부 함수는 클로저 안의 값만 읽음 + // eslint-disable-next-line react-hooks/exhaustive-deps + [state, calcMode, alreadyDone, remaining, weeklyEarn, doneEarn, monthlyEarn, monthlyDoneThisMonth], + ) const isDone = completionDate !== null const [resetOpen, setResetOpen] = useState(false) diff --git a/frontend/src/features/symbol/pc/Symbol.jsx b/frontend/src/features/symbol/pc/Symbol.jsx index 36d7bd7..9b57089 100644 --- a/frontend/src/features/symbol/pc/Symbol.jsx +++ b/frontend/src/features/symbol/pc/Symbol.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useLayoutEffect, useMemo } from 'react' +import { memo, useState, useEffect, useLayoutEffect, useMemo } from 'react' import { useQuery, useQueries, useMutation } from '@tanstack/react-query' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' @@ -9,6 +9,7 @@ import Select from '../../../components/common/Select' import Tooltip from '../../../components/common/Tooltip' import CharacterSuggestDropdown from '../../../components/common/CharacterSuggestDropdown' import { useSymbolStore } from '../store' +import { formatMesoKorean } from '../../../utils/formatting' dayjs.extend(utc) dayjs.extend(timezone) @@ -44,20 +45,9 @@ function computeCompletion({ remainingSymbols, daily, weeklyPerWeek, extra, dail return { days: null, date: null } } -function formatMesoKorean(n) { - const v = Number(n) || 0 - if (v <= 0) return '0' - const eok = Math.floor(v / 100_000_000) - const man = Math.floor((v % 100_000_000) / 10_000) - const parts = [] - if (eok) parts.push(`${eok.toLocaleString()}억`) - if (man) parts.push(`${man.toLocaleString()}만`) - return parts.length ? parts.join(' ') : v.toLocaleString() -} - const TYPE_ORDER = ['아케인', '어센틱', '그랜드 어센틱'] -function CharacterCard({ char, active, onSelect, onRemove }) { +const CharacterCard = memo(function CharacterCard({ char, active, onSelect, onRemove }) { return (