Skip to content

Comments

Fix(client): 리마인드 수정 모달 state 오류#156

Merged
jjangminii merged 4 commits intodevelopfrom
fix/#155/remind-edit-time
Sep 29, 2025
Merged

Fix(client): 리마인드 수정 모달 state 오류#156
jjangminii merged 4 commits intodevelopfrom
fix/#155/remind-edit-time

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Sep 26, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

리마인드 수정시 오류 1 : 리마인드 edit 창을 키고 닫을 때마다 시간이 제멋대로 지정되는 오류 발생
리마인드 수정시 오류 2 : 리마인드 edit 창을 키고 저장할 때마다 시간이 -9시간 저장되는 오류 발생

⭐ PR Point (To Reviewer)

리마인드 수정시 오류 1

원인

React는 컴포넌트의 state를 트리에서의 위치에 묶어서 보존하는데 같은 컴포넌트를 같은 위치에 계속 렌더링하면 이전 state를 그대로 유지해버려요. 그래서 북마크에서 모달을 열 때마다 이전 아티클에서 쓰던 내부 state(날짜/시간)가 carry-over”되는 현상이 발생했습니다.

“같은 위치에 렌더된 동일 컴포넌트는 상태가 보존된다.” 즉, 부모가 모달을 조건부로 토글만 하고, 모달의 위치/타입이 같다면 상태가 초기화되지 않게됩니다.

해결 방법

React는 같은 위치에 같은 타입의 컴포넌트가 렌더되면 기존 인스턴스를 재사용합니다. 그래서 prevData가 바뀌어도 모달은 그대로고 내부 useState 값들이 유지되는 state의 carry-over현상이 발생했어요.
그래서 리액트에서는 다른 대상을 보여줄 때처럼 컴포넌트 트리의 정체성이 바뀌어야 하는 경우 key를 달아 새 인스턴스로 간주시키라고 말하고있어요. key={articleDetail.id} 를 주면, id가 달라질 때마다 다른 컴포넌트로 간주되어 기존 것을 언마운트하며 새로 마운트며 내부 state가 초기화됩니다. ❇️

리마인드 수정시 오류 2

.. 얘는 별거 없었어요
읽을 땐 UTC→로컬 변환을 안 했는데, 저장할 땐 로컬→UTC 변환을 해서 매 저장마다 –9시간씩 밀렸습니다
extension과 같은 포맷 방식으로 수정 했습니다

📷 Screenshot

Summary by CodeRabbit

  • 신규 기능

    • 날짜/시간 입력 지원 확대: 다양한 포맷(YYYYMMDD, YYYY.MM.DD, HHmm, ‘오전/오후 HH:mm’) 자동 정규화 및 24시간 변환, 날짜·시간 결합 문자열 제공.
    • 편집 모달이 최신 기사 정보로 안정적으로 갱신되도록 동작 개선.
  • 버그 수정

    • 기사 변경 시 편집 모달이 업데이트되지 않던 문제 해결(key 적용).
    • 알림 시간 계산의 지역화/포맷 일관성 개선으로 잘못된 시간 저장 방지.
    • 아콘 조회 요청의 쿼리 구성 수정으로 간헐적 실패 해소.
  • 기타

    • 불필요한 콘솔 로그 제거.

@jjangminii jjangminii linked an issue Sep 26, 2025 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Sep 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
pinback-client-client Ready Ready Preview Comment Sep 26, 2025 7:35pm
pinback-client-landing Ready Ready Preview Comment Sep 26, 2025 7:35pm

@coderabbitai
Copy link

coderabbitai bot commented Sep 26, 2025

Walkthrough

MyBookmark/Remind의 CardEditModalkey={articleDetail.id}를 추가해 articleDetail 변경 시 모달이 재마운트되도록 했고, axios의 getAcorns 요청 URL을 /api/v1/users/acorns?로 조정했습니다. datetime 유틸은 buildUtcIso를 제거하고 combineDateTime 등 새로운 함수로 재구성되었습니다. (50단어 이하)

Changes

Cohort / File(s) Summary
Edit Modal key 적용
apps/client/src/pages/myBookmark/MyBookmark.tsx, apps/client/src/pages/remind/Remind.tsx
CardEditModal 렌더링에 key={articleDetail.id} 추가로 articleDetail 변경 시 컴포넌트 재마운트 보장
CardEditModal: remindTime 계산 변경
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx
remindTime 계산을 기존 buildUtcIso 호출에서 combineDateTime 호출로 교체
Datetime 유틸 리팩터링
apps/client/src/shared/utils/datetime.ts
buildUtcIso 제거, updateDate, updateTime, to24Hour, combineDateTime 등 새 공개 함수 추가 및 날짜/시간 정규화 로직 전환
Axios API 경로 조정
apps/client/src/shared/apis/axios.ts
getAcorns 요청 URL을 '/api/v1/users/acorns?'로 변경(쿼리 { now } 전달 유지)
소소한 정리
apps/client/src/pages/myBookmark/MyBookmark.tsx
불필요한 console.log(category) 제거

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as 사용자
  participant P as Page (MyBookmark/Remind)
  participant M as CardEditModal
  participant D as datetime 유틸

  U->>P: 편집 버튼 클릭
  P->>P: articleDetail 설정/변경
  Note right of P #D6EAF8: 렌더 시 key = articleDetail.id
  P->>M: render CardEditModal (key=articleDetail.id, articleDetail)
  alt articleDetail 변경 → key 변경
    Note over M #D5F5E3: React가 컴포넌트 재마운트 수행
    M->>D: combineDateTime(date, time)
    D-->>M: 정규화된 datetime 또는 null
    M-->>P: 최신 articleDetail 기반 폼 초기화
  else key 동일
    M-->>P: 기존 상태 유지
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

재림, frontend

Suggested reviewers

  • jllee000
  • constantly-dev

Poem

토끼가 전하네, 깡총깡총 🐇
키 하나 달렸더니 모달이 다시 솟고,
시간이 단정히 줄서고,
요청 길도 바르게 놓였네.
깡충—코드에 봄이 왔구나!

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning PR은 #155에서 요구한 모달 상태 초기화 및 시간 변환 오류 수정 요구사항을 충실히 반영했으나 #25의 프로그래스 바 구현 요구사항은 전혀 다루고 있지 않습니다. 관련 이슈 목록에서 PR과 무관한 #25를 제거하거나 별도 PR로 분리해 주세요.
Out of Scope Changes Check ⚠️ Warning PR에는 리마인드 모달과 datetime 유틸 관련 변경 외에도 MyBookmark.tsx의 콘솔 로그 제거 및 axios의 getAcorns 엔드포인트 수정처럼 이슈 범위와 직접 관련 없는 코드 변경이 포함되어 있습니다. 이슈 범위 외 변경 사항은 별도 PR로 분리하거나 이슈 요구사항에 맞게 제외해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 제목에는 모달 상태 오류 수정이 명시되어 있으나 시간대 변환 수정이 누락되어 전체 변경 내용을 충분히 반영하지 못하고 있습니다.
Description Check ✅ Passed 작성된 PR 설명은 Related Issues, Tasks, PR Point 섹션을 모두 포함하고 있으며 각 오류의 원인과 해결 방법을 구체적으로 기술하여 템플릿 구조를 충실히 따르고 있습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#155/remind-edit-time

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5b4d62 and 857ea61.

📒 Files selected for processing (1)
  • apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (1)
apps/client/src/shared/utils/datetime.ts (1)
  • combineDateTime (29-54)
🔇 Additional comments (1)
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (1)

88-89: combineDateTime 적용으로 타임존 드리프트 방지

로컬에서 선택한 날짜/시간을 그대로 결합해 전달하게 되어 이전 UTC 재계산(-9시간) 문제가 재발하지 않겠습니다. isRemindOn && date && time 가드도 있어 안전하게 null을 회피합니다.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the fix 버그 수정하라 러브버그 label Sep 26, 2025
@github-actions
Copy link

github-actions bot commented Sep 26, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/client/src/shared/apis/axios.ts (1)

26-28: 요청 URL에서 불필요한 ? 제거 제안

현재 '/api/v1/users/acorns?' 형태로 남아 있어서 Axios가 최종적으로 .../acorns?&now=... 같은 문자열을 만들 가능성이 있습니다. 대부분의 서버는 처리하지만 쓸데없는 빈 쿼리 파라미터가 붙어 가독성이 떨어질 수 있으니 ?를 제거해 두는 편이 깔끔합니다.

-  const { data } = await apiRequest.get('/api/v1/users/acorns?', {
+  const { data } = await apiRequest.get('/api/v1/users/acorns', {
     params: { now },
   });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae83b9a and 5c17482.

📒 Files selected for processing (3)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx (1 hunks)
  • apps/client/src/pages/remind/Remind.tsx (1 hunks)
  • apps/client/src/shared/apis/axios.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook
🔇 Additional comments (2)
apps/client/src/pages/remind/Remind.tsx (1)

195-198: 모달 상태 초기화 잘 처리됐습니다

key={articleDetail.id} 덕분에 아티클을 바꿔 열 때마다 CardEditModal이 재마운트되어 내부 상태가 초기화됩니다. 기대한 이슈 방지에 적절한 접근입니다.

apps/client/src/pages/myBookmark/MyBookmark.tsx (1)

244-247: 동일한 키 전략으로 북마크 편집도 안전해졌습니다

여기서도 key={articleDetail.id}를 부여해 편집 모달이 다른 아티클로 전환될 때마다 초기화되도록 한 점이 좋습니다. Remind 페이지와 일관성 있게 동작하겠습니다.

@jjangminii jjangminii changed the title Fix/#155/remind edit time Fix(client): 리마인드 수정 모달 state 오류 Sep 26, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/client/src/shared/utils/datetime.ts (1)

13-27: to24Hour: 로케일/포맷 다양성 보강 제안 (AM/PM, 초 지원, 느슨한 공백)

현재 정규식은 "오전/오후 HH:mm"만 처리합니다. AM/PM, 초(SS), 공백 변형까지 허용하면 호환성이 높아집니다. 또한 입력이 빈 문자열일 때의 처리도 빠른 리턴으로 명시하는 편이 안전합니다.

아래와 같이 보강을 제안합니다:

-export const to24Hour = (time: string) => {
-  const match = time.match(/(오전|오후)\s(\d{1,2}):(\d{2})/);
-  if (!match) return time;
-
-  const [, period, hourStr, minute] = match;
-  let hour = parseInt(hourStr, 10);
-
-  if (period === '오전' && hour === 12) {
-    hour = 0;
-  } else if (period === '오후' && hour !== 12) {
-    hour += 12;
-  }
-
-  return `${hour.toString().padStart(2, '0')}:${minute}`;
-};
+export const to24Hour = (time: string) => {
+  if (!time) return '';
+  // 허용 예: "오전 9:05", "오후10:30", "AM 9:05", "pm 12:30:10"
+  const match = time.match(
+    /^(?:(오전|오후)|(?:(AM|PM)))\s?(\d{1,2}):(\d{2})(?::(\d{2}))?$/i
+  );
+  if (!match) return time;
+
+  const [, koPeriod, enPeriod, hourStr, minute, second] = match;
+  const isPM = (koPeriod === '오후') || (enPeriod?.toLowerCase() === 'pm');
+  let hour = parseInt(hourStr, 10) % 12; // 12 → 0으로 정규화
+  if (isPM) hour += 12;
+  const hh = hour.toString().padStart(2, '0');
+  const ss = second ? `:${second}` : '';
+  return `${hh}:${minute}${ss}`;
+};
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (2)

22-24: date/time 유틸 import 경로 혼재 — 단일 소스(datetime.ts)로 통일 권장

동일 목적의 유틸이 서로 다른 모듈에서 임포트되고 있습니다. 한 파일(datetime.ts)로 일원화하면 추후 동작 불일치/이중 유지보수를 줄일 수 있습니다.

-import { combineDateTime } from '@shared/utils/datetime';
-import { updateDate, updateTime } from '@shared/utils/formatDateTime';
+import { combineDateTime, updateDate, updateTime } from '@shared/utils/datetime';

89-90: 유효성 에러/미입력 시 저장 차단 로직 추가 권장

현재 date/time에 에러가 있어도 remindTime이 null 또는 잘못된 값으로 저장될 수 있습니다. 저장 전에 에러/미입력 시 early-return 하도록 보강을 권합니다.

예시 구현(참고용):

const saveData = () => {
  if (!prevData?.id) { /* 그대로 */ }

  if (isRemindOn) {
    if (!date || !time || dateError || timeError) {
      setToastIsOpen(true);
      return;
    }
  }

  const remindTime = isRemindOn ? combineDateTime(date, time) : null;
  // 이하 동일
};
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c17482 and c5b4d62.

📒 Files selected for processing (3)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx (1 hunks)
  • apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (3 hunks)
  • apps/client/src/shared/utils/datetime.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (1)
apps/client/src/shared/utils/datetime.ts (1)
  • combineDateTime (29-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook
🔇 Additional comments (2)
apps/client/src/shared/utils/datetime.ts (2)

2-5: updateDate 유틸은 간결하고 목적에 부합합니다

단순 포맷 변환 로직으로 문제 없어 보입니다.


8-11: updateTime 유틸 처리 적절

HH:mm:ss 입력에 대해 UI 표기용 HH:mm으로 잘라내는 동작이 명확합니다.

Comment on lines +29 to +54
export const combineDateTime = (date: string, time: string) => {
if (!date || !time) return null;

// date가 YYYYMMDD 형태라면 가공 필요
let formattedDate = date;
if (/^\d{8}$/.test(date)) {
const year = date.slice(0, 4);
const month = date.slice(4, 6);
const day = date.slice(6, 8);
formattedDate = `${year}-${month}-${day}`;
} else {
formattedDate = date.replace(/\./g, '-'); // YYYY.MM.DD → YYYY-MM-DD
}

// time이 HHmm 형태라면 HH:mm으로 변환
let normalizedTime = time;
if (/^\d{4}$/.test(time)) {
normalizedTime = `${time.slice(0, 2)}:${time.slice(2, 4)}`;
}

// 24시간 포맷 변환 함수가 있다면 적용
const to24 = to24Hour ? to24Hour(normalizedTime) : normalizedTime;
const formattedTime = to24.length === 5 ? `${to24}:00` : to24;

return `${formattedDate}T${formattedTime}`;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

combineDateTime: 타임존/UTC 계약 확인 필요 + 파싱 실패 가드 추가 제안

  • 현재 반환값은 "YYYY-MM-DDTHH:mm(:ss)"의 로컬 타임스탬프(타임존 미포함)입니다. 이전 buildUtcIso(UTC ISO 생성) 대비 의미가 바뀌었을 수 있어 서버가 기대하는 시간대(로컬/UTC/오프셋 포함)를 확인해야 합니다. 계약이 UTC라면 now는 UTC(ISOString)인데 remindTime은 로컬(오프셋 없음)이라 불일치합니다.
  • to24Hour 존재 여부 체크는 항상 truthy라 의미가 없어지고, 변환 실패 시에도 그대로 문자열을 이어붙이면 유효하지 않은 ISO가 생성될 수 있습니다.

아래는 안전성 향상을 위한 최소 수정입니다(형식 검증 + 명시적 반환 타입 + 불필요 조건 제거):

-export const combineDateTime = (date: string, time: string) => {
+export const combineDateTime = (date: string, time: string): string | null => {
   if (!date || !time) return null;
@@
-  // 24시간 포맷 변환 함수가 있다면 적용
-  const to24 = to24Hour ? to24Hour(normalizedTime) : normalizedTime;
-  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
+  // 24시간 포맷 변환
+  const to24 = to24Hour(normalizedTime);
+  // 유효한 HH:mm(:ss) 아니면 null
+  if (!/^\d{2}:\d{2}(?::\d{2})?$/.test(to24)) return null;
+  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
 
   return `${formattedDate}T${formattedTime}`;
 };

서버가 UTC를 요구한다면 아래 대안으로 변환하십시오(로컬 해석 → UTC ISO). 유효하지 않은 날짜면 null 반환:

export const combineDateTimeUTC = (date: string, time: string): string | null => {
  const local = combineDateTime(date, time);
  if (!local) return null;
  const d = new Date(local); // 로컬로 파싱
  if (Number.isNaN(d.getTime())) return null;
  return d.toISOString(); // UTC ISO (Z 포함)
};

검증 스크립트(레포 루트에서 실행):


🏁 Script executed:

#!/bin/bash
# 1) 기존 buildUtcIso 잔존 사용처 확인
rg -nP -C2 '\bbuildUtcIso\s*\('

# 2) EditArticleRequest 내 remindTime 타입/계약 확인
rg -nPU -C3 '(?s)\binterface\s+EditArticleRequest\b.*?\{.*?\n.*remindTime.*\n.*\}'

# 3) remindTime을 서버에 전송하는 쿼리 함수 확인
rg -nP -C3 'usePutEditArticle|remindTime'

# 4) 서버가 기대하는 포맷(UTC/오프셋/로컬) 문서/주석 단서 탐색
rg -nP -C2 'remind|timezone|utc|offset|Z\b|ISO'

Length of output: 289459


🏁 Script executed:

#!/bin/bash
set -eo pipefail

echo "=== Show file: apps/client/src/shared/utils/datetime.ts ==="
sed -n '1,240p' apps/client/src/shared/utils/datetime.ts || true

echo
echo "=== Search for to24Hour, combineDateTime, buildUtcIso across repo ==="
rg -n --hidden --no-ignore-vcs 'to24Hour|combineDateTime|buildUtcIso' || true

echo
echo "=== Show remindTimeFormat utility (used by extension imports) if exists ==="
rg -n --hidden --no-ignore-vcs 'remindTimeFormat' -S || true
rg -n --hidden --no-ignore-vcs '@utils/remindTimeFormat' -S || true

Length of output: 2991


combineDateTime: 타임존 계약 확인 + 파싱 실패 방어(클라이언트·익스텐션 동시 수정 필요)

combineDateTime은 타임존/오프셋 정보 없이 로컬 형식으로 반환하며(to24Hour 호출 후 "YYYY-MM-DDTHH:mm(:ss)" 형태), to24Hour 존재 체크는 무의미합니다. 입력 유효성(날짜/시간 파싱 실패) 처리와 명시적 반환 타입을 추가하고, 서버가 UTC를 요구하면 변환 로직을 별도 함수로 제공하세요.

수정 위치(정확한 포인트): apps/client/src/shared/utils/datetime.ts, apps/extension/src/utils/remindTimeFormat.ts
호출부 확인: apps/client/src/shared/components/cardEditModal/CardEditModal.tsx, apps/extension/src/pages/MainPop.tsx
서버 타입 참조: apps/client/src/shared/types/api.ts (remindTime: string | null) — 서버가 UTC(Z 포함)를 기대하는지 확인 필요.

권장 최소 변경(파일 내 combineDateTime 대체안 — 유효성 검증 + 타입 명시):

-export const combineDateTime = (date: string, time: string) => {
+export const combineDateTime = (date: string, time: string): string | null => {
   if (!date || !time) return null;
@@
-  // 24시간 포맷 변환 함수가 있다면 적용
-  const to24 = to24Hour ? to24Hour(normalizedTime) : normalizedTime;
-  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
+  // 24시간 포맷 변환
+  const to24 = to24Hour(normalizedTime);
+  if (!/^\d{2}:\d{2}(?::\d{2})?$/.test(to24)) return null;
+  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
 
   return `${formattedDate}T${formattedTime}`;
 };

서버가 UTC ISO(Z)를 요구한다면(로컬 해석 → UTC ISO 변환) 보조 함수 추가:

export const combineDateTimeUTC = (date: string, time: string): string | null => {
  const local = combineDateTime(date, time);
  if (!local) return null;
  const d = new Date(local); // 로컬로 파싱
  if (Number.isNaN(d.getTime())) return null;
  return d.toISOString(); // UTC ISO (Z 포함)
};

검증 요약: 리포지토리에 동일 로직이 client/extension에 존재함을 확인(동기 수정 필요). 서버가 UTC를 요구할 경우 클라이언트에서 toISOString()으로 변환해 전송하거나 서버 계약에 맞게 명확히 합의해야 합니다.

Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해결 대박~~ 고생하셨습니다 👍

@jjangminii jjangminii merged commit 4c01907 into develop Sep 29, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix 버그 수정하라 러브버그

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix] 리마인드 수정시 오류

2 participants