Skip to content

[Task] GRC-02 프런트엔드 lint debt 1차 정리#5

Merged
alexization merged 2 commits intodevelopfrom
feat/grc-02-frontend-lint-debt
Mar 26, 2026
Merged

[Task] GRC-02 프런트엔드 lint debt 1차 정리#5
alexization merged 2 commits intodevelopfrom
feat/grc-02-frontend-lint-debt

Conversation

@alexization
Copy link
Copy Markdown
Owner

@alexization alexization commented Mar 26, 2026

1) 요약

  • react-hooks/set-state-in-effect, react-hooks/static-components, react-hooks/purity, react-hooks/use-memo를 포함한 기존 lint warning 26개를 정리했습니다.
  • mounted/hydration/media-query 패턴을 shared hook 기반으로 정리해 warning 없이 같은 동작을 유지하도록 맞췄습니다.

2) 연관 이슈

3) 사용자 관점 결과

  • 해결하려는 문제: lint 신호에 기존 debt가 섞여 후속 작업에서 회귀 판단이 어려운 상태
  • 사용자에게 달라지는 점: 사용자 기능 변경은 없고, 정적 가드레일 신호 품질이 개선됩니다.
  • 비목표: build/runtime 문제 해결, 디자인 개편, Playwright 도입

4) 영향 범위

  • 변경된 route / page / component:
    • src/app/users/[username]/user-profile-client.tsx
    • src/app/global-error.tsx
    • src/features/home/components/hero-section.tsx
    • src/features/user/components/badge-generator.tsx
    • src/features/user/components/stats-chart-impl.tsx
    • src/shared/components/theme-toggle.tsx
    • src/shared/components/ui/heatmap-background.tsx
  • 상태 관리 / 데이터 로딩 영향:
    • useAuthHydrated, media query/reduced motion, locale sync, throttle hook 구현 정리
  • API 계약 영향:
    • 없음
  • 접근성 / 반응형 영향:
    • 의도적 변경 없음. mounted/media-query 처리만 정리

5) 스크린샷 또는 영상

  • before: 없음. 시각 변경 목적의 PR이 아님
  • after: 없음. 시각 변경 목적의 PR이 아님

6) 검증 증거

유형 명령어 / 증거 결과
Lint npm run lint 성공, warning 0
Build npm run build 실패. 기존 이슈인 Google Fonts fetch 및 middleware.ts deprecated convention으로 중단
Automated Test npx tsc --noEmit 성공
Playwright 미구축(GRC-04 예정) 미실행
Browser QA 수동 브라우저 QA 미실행

7) API / 백엔드 연동 확인

  • 필요한 API 응답: 없음. 기존 ranking/user/auth contract 위에서 lint cleanup만 수행
  • Mock 여부: 없음
  • 관련 backend 변경: 없음

8) 리스크 및 롤백

  • 리스크: mounted/hydration timing을 shared hook으로 옮겼기 때문에 home/header/user 화면의 상호작용은 한 번 더 보는 편이 좋습니다.
  • 롤백 계획: PR revert로 lint cleanup 이전 구현으로 복구 가능

9) 체크리스트

  • 연관 이슈가 연결되어 있음
  • 스크린샷 또는 영상이 포함되었거나 불필요 사유를 적음
  • Lint / Build 결과가 기입되어 있음
  • 브라우저 QA 또는 Playwright 결과가 기입되어 있음
  • API 계약 영향이 반영되었거나 없음을 명시함

Summary by CodeRabbit

  • Improvements

    • Search input behavior refined for handling recent search selections
    • Heatmap background now renders with deterministic patterns instead of random generation
    • Component hydration and state synchronization optimized across the application
  • Chores

    • Build configuration updated to use ES modules

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b1253cea-3de0-4f55-9512-d375fb596672

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/grc-02-frontend-lint-debt

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.

@alexization alexization self-assigned this Mar 26, 2026
Copy link
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/hooks/use-throttle.ts (1)

17-39: ⚠️ Potential issue | 🟡 Minor

Potential stale closure: pending RAF may invoke outdated callback.

When callback changes, useCallback creates a new throttled function, but any RAF already scheduled (line 33-37) still captures the old callback reference. The cleanup effect only runs on unmount (empty deps), not when callback changes, so the stale callback could be invoked.

Consider using a ref to always call the latest callback:

🛡️ Proposed fix using a callback ref
 import { useCallback, useEffect, useRef } from 'react'
 
 export function useThrottledCallback<TArgs extends unknown[]>(
     callback: (...args: TArgs) => void,
     delay: number = 16
 ): (...args: TArgs) => void {
     const lastCallRef = useRef<number>(0)
     const rafRef = useRef<number | null>(null)
+    const callbackRef = useRef(callback)
+
+    // Keep callback ref fresh so RAF always calls latest version
+    callbackRef.current = callback
 
     useEffect(() => {
         return () => {
             if (rafRef.current !== null) {
                 cancelAnimationFrame(rafRef.current)
             }
         }
     }, [])
 
-    return useCallback((...args: TArgs) => {
+    return useCallback((...args: TArgs) => {
             const now = performance.now()
 
             if (now - lastCallRef.current >= delay) {
                 lastCallRef.current = now
-                callback(...args)
+                callbackRef.current(...args)
             } else if (!rafRef.current) {
                 rafRef.current = requestAnimationFrame(() => {
                     lastCallRef.current = performance.now()
                     rafRef.current = null
-                    callback(...args)
+                    callbackRef.current(...args)
                 })
             }
-        }, [callback, delay])
+        }, [delay])
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/hooks/use-throttle.ts` around lines 17 - 39, The scheduled RAF can
call a stale callback when `callback` changes; introduce a mutable ref (e.g.,
`callbackRef`) and update `callbackRef.current = callback` in a small effect
that runs on `[callback]`, then invoke `callbackRef.current(...args)` everywhere
(both in the immediate path and inside the `requestAnimationFrame` handler)
instead of using the `callback` closed over by `useCallback`; keep `rafRef`,
`lastCallRef`, and the existing unmount cleanup that cancels with
`cancelAnimationFrame(rafRef.current)` but remove `callback` from the
`useCallback` deps so the throttled function doesn’t recreate on each callback
change.
🧹 Nitpick comments (2)
src/features/user/components/badge-generator.tsx (1)

19-46: Good refactor: extracting BadgeCopyButton as a static component.

This correctly addresses the lint warning by moving the component outside the parent, avoiding re-creation on each render.

Minor type refinement: The type prop accepts CopyType (which includes null), but the buttons always pass a specific type. Consider narrowing this for better type safety:

🔧 Optional type refinement
 type CopyType = "markdown" | "html" | "link" | null
 
+type BadgeCopyType = Exclude<CopyType, null>
+
 interface BadgeCopyButtonProps {
     copied: CopyType
     icon: LucideIcon
     label: string
     onCopy: () => void
-    type: CopyType
+    type: BadgeCopyType
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/user/components/badge-generator.tsx` around lines 19 - 46, The
BadgeCopyButtonProps currently types the prop `type` as CopyType (which can be
null) but every caller passes a concrete copy identifier; update the prop to a
non-nullable variant (e.g., use NonNullable<CopyType> or Exclude<CopyType,
null>) so `BadgeCopyButton`'s `type` cannot be null and improves type safety;
adjust the BadgeCopyButtonProps declaration (and any related usages if
necessary) to use the narrowed type while keeping `copied: CopyType` as-is.
src/shared/components/ui/heatmap-background.tsx (1)

14-21: Consider throttling resize events to avoid excessive re-renders.

The resize event fires rapidly during window resizing. Each event triggers useSyncExternalStore to call getSnapshot, and if the width changes, buildBlocks recomputes all block properties. For a decorative background, this frequency may cause performance degradation on lower-end devices.

♻️ Throttled resize subscription
 function subscribeToWindowResize(onStoreChange: () => void) {
     if (typeof window === "undefined") {
         return () => {}
     }

-    window.addEventListener("resize", onStoreChange)
-    return () => window.removeEventListener("resize", onStoreChange)
+    let timeoutId: ReturnType<typeof setTimeout> | null = null
+    const throttledHandler = () => {
+        if (timeoutId) return
+        timeoutId = setTimeout(() => {
+            timeoutId = null
+            onStoreChange()
+        }, 100)
+    }
+
+    window.addEventListener("resize", throttledHandler)
+    return () => {
+        window.removeEventListener("resize", throttledHandler)
+        if (timeoutId) clearTimeout(timeoutId)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/components/ui/heatmap-background.tsx` around lines 14 - 21, The
resize subscription currently calls onStoreChange for every resize event;
throttle it to reduce re-renders by wrapping the listener in a rate-limiter
(e.g., requestAnimationFrame or a small timeout-based throttle) inside
subscribeToWindowResize so onStoreChange is invoked at most once per animation
frame (or at a chosen interval). Implement the throttling wrapper around the
event callback in subscribeToWindowResize, ensure the returned cleanup removes
the throttled listener and cancels any pending rAF/timeouts, and keep references
to the original onStoreChange for removeEventListener so cleanup works
correctly; this will reduce how often getSnapshot()/buildBlocks recompute during
window resize.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/shared/providers/locale-provider.tsx`:
- Around line 41-42: The fallback localeState should track the most recent
pathname-derived locale so unprefixed routes use that value; compute pathLocale
= getLocaleFromPathname(pathname) and use locale = pathLocale ?? localeState,
and add an effect that whenever pathLocale is non-null it calls
setLocaleState(pathLocale) so the fallback stays synchronized; ensure this keeps
setLocale functioning for unprefixed routes while allowing pathname-derived
locale to override when present (referencing getLocaleFromPathname, localeState,
setLocaleState, pathname, and the existing useEffect).

---

Outside diff comments:
In `@src/shared/hooks/use-throttle.ts`:
- Around line 17-39: The scheduled RAF can call a stale callback when `callback`
changes; introduce a mutable ref (e.g., `callbackRef`) and update
`callbackRef.current = callback` in a small effect that runs on `[callback]`,
then invoke `callbackRef.current(...args)` everywhere (both in the immediate
path and inside the `requestAnimationFrame` handler) instead of using the
`callback` closed over by `useCallback`; keep `rafRef`, `lastCallRef`, and the
existing unmount cleanup that cancels with
`cancelAnimationFrame(rafRef.current)` but remove `callback` from the
`useCallback` deps so the throttled function doesn’t recreate on each callback
change.

---

Nitpick comments:
In `@src/features/user/components/badge-generator.tsx`:
- Around line 19-46: The BadgeCopyButtonProps currently types the prop `type` as
CopyType (which can be null) but every caller passes a concrete copy identifier;
update the prop to a non-nullable variant (e.g., use NonNullable<CopyType> or
Exclude<CopyType, null>) so `BadgeCopyButton`'s `type` cannot be null and
improves type safety; adjust the BadgeCopyButtonProps declaration (and any
related usages if necessary) to use the narrowed type while keeping `copied:
CopyType` as-is.

In `@src/shared/components/ui/heatmap-background.tsx`:
- Around line 14-21: The resize subscription currently calls onStoreChange for
every resize event; throttle it to reduce re-renders by wrapping the listener in
a rate-limiter (e.g., requestAnimationFrame or a small timeout-based throttle)
inside subscribeToWindowResize so onStoreChange is invoked at most once per
animation frame (or at a chosen interval). Implement the throttling wrapper
around the event callback in subscribeToWindowResize, ensure the returned
cleanup removes the throttled listener and cancels any pending rAF/timeouts, and
keep references to the original onStoreChange for removeEventListener so cleanup
works correctly; this will reduce how often getSnapshot()/buildBlocks recompute
during window resize.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b8c150c-14b7-4b00-bb9c-942d882343ea

📥 Commits

Reviewing files that changed from the base of the PR and between 9515b1e and b0cf501.

📒 Files selected for processing (18)
  • src/app/global-error.tsx
  • src/app/users/[username]/user-profile-client.tsx
  • src/features/auth/store/auth-store.ts
  • src/features/home/components/hero-section.tsx
  • src/features/ranking/api/ranking-service.ts
  • src/features/user/components/badge-generator.tsx
  • src/features/user/components/score-info-modal.tsx
  • src/features/user/components/stats-chart-impl.tsx
  • src/shared/components/command.tsx
  • src/shared/components/input.tsx
  • src/shared/components/theme-toggle.tsx
  • src/shared/components/ui/heatmap-background.tsx
  • src/shared/hooks/use-has-mounted.ts
  • src/shared/hooks/use-media-query.ts
  • src/shared/hooks/use-reduced-motion.ts
  • src/shared/hooks/use-throttle.ts
  • src/shared/providers/locale-provider.tsx
  • tailwind.config.ts
💤 Files with no reviewable changes (1)
  • src/shared/components/command.tsx

Copy link
Copy Markdown
Owner Author

리뷰 코멘트 반영했습니다.

  • src/shared/providers/locale-provider.tsx: unprefixed route에서 마지막 pathname-derived locale이 fallback으로 유지되도록 동기화 경로 보완
  • src/shared/hooks/use-throttle.ts: pending RAF가 stale callback을 호출하지 않도록 callbackRef 적용
  • src/shared/components/ui/heatmap-background.tsx: resize subscription을 requestAnimationFrame 기준으로 throttle
  • src/features/user/components/badge-generator.tsx: BadgeCopyButtontype을 non-nullable로 축소

재검증:

  • npm run lint
  • npx tsc --noEmit

반영 커밋: a73557a

Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/shared/components/ui/heatmap-background.tsx`:
- Around line 61-65: The seed inputs for each block (activeSeed, opacitySeed,
durationSeed, delaySeed, colorSeed) should not include the viewport-dependent
variable width because that causes reseeding on initial render/resizes; update
the seededValue calls in heatmap-background.tsx to use only index-based values
(e.g., index with distinct fixed offsets or multipliers per seed) so each seed
remains stable across width changes while keeping column-count logic separate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c2344be7-f87c-4e93-9a04-3bc58f1100fc

📥 Commits

Reviewing files that changed from the base of the PR and between b0cf501 and a73557a.

📒 Files selected for processing (4)
  • src/features/user/components/badge-generator.tsx
  • src/shared/components/ui/heatmap-background.tsx
  • src/shared/hooks/use-throttle.ts
  • src/shared/providers/locale-provider.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/shared/providers/locale-provider.tsx
  • src/features/user/components/badge-generator.tsx
  • src/shared/hooks/use-throttle.ts

@alexization alexization merged commit 5f6a6dd into develop Mar 26, 2026
3 checks passed
@alexization alexization deleted the feat/grc-02-frontend-lint-debt branch March 26, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant