Skip to content

fix: 修复排行榜 Tab 切换无限循环问题, close #177#178

Merged
ding113 merged 1 commit intodevfrom
fix/issue-177-leaderboard-tab-switching
Nov 22, 2025
Merged

fix: 修复排行榜 Tab 切换无限循环问题, close #177#178
ding113 merged 1 commit intodevfrom
fix/issue-177-leaderboard-tab-switching

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Nov 22, 2025

Summary

修复排行榜页面 Tab 切换时触发无限循环导致页面卡死的问题。

Problem

PR #168 (commit 281ed80) 引入了一个 bug:在 useEffect 依赖数组中包含了 scopeperiod 状态变量。当这些状态被更新时,会重新触发 useEffect,而 useEffect 内部又会检查并更新这些状态,导致无限循环。

Solution

useEffect 依赖数组中移除 scopeperiod,因为:

  • 这两个变量仅在 effect 内部作为比较目标使用
  • 真正需要响应的变化来源是 searchParams
  • 添加了 eslint-disable 注释说明原因

Changes

  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
    • 移除 scopeperiod 从 useEffect 依赖数组
    • 添加注释说明修改原因

Testing

  • Manual testing performed - 验证 Tab 切换不再触发无限循环
  • No breaking changes

Related Issues

Closes #177

- 从 useEffect 依赖数组移除 scope 和 period,避免状态更新触发循环
- 添加 eslint-disable 注释说明原因
- 修复由 PR #168 (commit 281ed80) 引入的 bug
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

这个拉取请求旨在解决排行榜视图中存在的无限循环问题,该问题在用户切换 Tab 时会不正确地触发状态更新。通过调整 useEffect 钩子的依赖项,确保了组件行为的稳定性,并避免了不必要的渲染循环,从而提升了用户体验。

Highlights

  • 修复无限循环问题: 从 useEffect 依赖数组中移除了 scope 和 period,以避免在排行榜 Tab 切换时因状态更新导致的无限循环。
  • ESLint 规则禁用: 添加了 eslint-disable-next-line react-hooks/exhaustive-deps 注释,明确说明了移除依赖的原因。
  • 修复历史引入的 bug: 此修复解决了由 PR feat: 供应商页面增加排行榜入口 #168 (commit 281ed80) 引入的一个 bug。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

此 PR 修复了排行榜 Tab 切换时因 useEffect 依赖项不当而导致的无限循环问题。通过从依赖数组中移除 scopeperiod 状态,成功解决了该 bug。这是一个正确的修复。

我有一个建议可以进一步优化代码:可以移除 useEffect 中的 if 判断,直接调用 setScopesetPeriod。React 会在状态值未改变时自动跳过重新渲染,这样做可以移除 eslint-disable 注释,使代码更简洁且符合 React Hooks 的最佳实践。

@ding113
Copy link
Owner Author

ding113 commented Nov 22, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

PR Summary

This PR contains a single change to fix a React useEffect infinite loop by removing scope and period from the dependency array in leaderboard-view.tsx.

Scanned Categories

  • ✅ Injection attacks (SQL, NoSQL, Command, LDAP, etc.)
  • ✅ Authentication and session management
  • ✅ Sensitive data exposure
  • ✅ Access control and authorization
  • ✅ Security misconfiguration
  • ✅ Cross-site scripting (XSS)
  • ✅ Insecure deserialization
  • ✅ SSRF and path traversal
  • ✅ Cryptographic weaknesses

Notes

The change modifies React hook dependencies but does not introduce any security concerns. The disabled ESLint rule is for the react-hooks/exhaustive-deps warning, which is a code quality concern, not a security vulnerability.


🤖 Automated security scan by Claude AI

@ding113 ding113 added size/XS Extra Small PR (< 50 lines) bug Something isn't working labels Nov 22, 2025
@ding113 ding113 merged commit 5059289 into dev Nov 22, 2025
9 checks passed
Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

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

🔴 Critical: Logic Error - Stale Closure Bug

Why this is a problem: By removing scope and period from the dependency array, the conditionals on lines 38 and 45 will use stale values captured at component mount time. This means:

  1. When the user clicks a tab to change scope or period, the effect won't re-run
  2. The comparisons normalizedScope !== scope and normalizedPeriod !== period will use the old values from the closure
  3. This creates incorrect behavior when URL params change while component is mounted

Why the infinite loop occurred: The infinite loop was caused by:

  • State update (setScope/setPeriod) → triggers effect (because deps include scope/period)
  • Effect runs → calls setScope/setPeriod again → triggers effect again → loop

Suggested fix - Use functional state updates to avoid needing the current value in deps:

useEffect(() => {
  const urlScope = searchParams.get("scope");
  const normalizedScope = urlScope === "provider" && isAdmin ? "provider" : "user";
  
  // Use functional update to compare with current value
  setScope(prevScope => prevScope !== normalizedScope ? normalizedScope : prevScope);

  const urlPeriod = searchParams.get("period");
  const normalizedPeriod = urlPeriod === "monthly" ? "monthly" : "daily";
  
  // Use functional update to compare with current value
  setPeriod(prevPeriod => prevPeriod !== normalizedPeriod ? normalizedPeriod : prevPeriod);
}, [isAdmin, searchParams]); // Only external deps, no state deps

This avoids the infinite loop because:

  • State setters with functional updates don't trigger re-render if value doesn't change (React optimization)
  • No need for scope/period in deps since we use the prev value from the setter
  • The effect only runs when URL params or isAdmin actually change

ding113 pushed a commit that referenced this pull request Nov 22, 2025
Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

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

🟡 Medium: Missing URL Synchronization

Why this is a problem: The tab handlers update local state but don't update the URL query parameters. This creates an inconsistency:

  1. User clicks "Provider Ranking" tab
  2. onValueChange updates local state: scope = "provider"
  3. URL remains unchanged (no query params)
  4. If user refreshes page, they'll see "User Ranking" again (URL has no ?scope=provider)
  5. The "deep linking" feature mentioned in the comment on line 33 doesn't work for user interactions

Current behavior:

  • External links with ?scope=provider&period=monthly work correctly
  • But clicking tabs doesn't update URL, so:
    • Can't share current view via URL
    • Browser back/forward buttons don't work as expected
    • Page refresh loses tab selection

Suggested fix - Add URL synchronization to tab handlers:

import { useRouter, usePathname } from "next/navigation";

export function LeaderboardView({ isAdmin }: LeaderboardViewProps) {
  // ... existing code ...
  const router = useRouter();
  const pathname = usePathname();

  const handleScopeChange = (newScope: typeof scope) => {
    setScope(newScope);
    const params = new URLSearchParams(searchParams.toString());
    params.set("scope", newScope);
    router.replace(pathname + "?" + params.toString(), { scroll: false });
  };

  const handlePeriodChange = (newPeriod: typeof period) => {
    setPeriod(newPeriod);
    const params = new URLSearchParams(searchParams.toString());
    params.set("period", newPeriod);
    router.replace(pathname + "?" + params.toString(), { scroll: false });
  };

  // ... later in JSX ...
  <Tabs value={scope} onValueChange={handleScopeChange}>
  <Tabs value={period} onValueChange={handlePeriodChange}>

This would make the URL truly the "source of truth" and the first useEffect would work correctly without conflicts.

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

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

⚠️ CORRECTION TO PREVIOUS REVIEW

I need to retract my previous comment about "stale closure bug". After deeper analysis, the current fix is actually correct and works as intended.

Why the fix works:

The effect closure captures scope and period values at the moment the effect runs, not at mount time. Since the effect only runs when searchParams or isAdmin change, it always has the current state values.

Trace through the scenarios:

  1. User clicks tab: Effect doesn't run (searchParams unchanged) → tab switches successfully ✅
  2. URL params change: Effect runs with current state values → correctly syncs URL to state ✅
  3. No infinite loop: Removing state from deps breaks the loop while preserving functionality ✅

The real issue is the one I mentioned in my second comment: missing URL synchronization when users click tabs. The current fix solves the infinite loop bug correctly, but leaves room for improvement in URL synchronization.

Recommendation: The current fix can be merged as-is to resolve the blocking issue #177. The URL synchronization enhancement can be addressed in a follow-up PR if desired.

My apologies for the confusion in my initial review.

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

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

📋 Code Review Summary

This PR successfully fixes the infinite loop bug in leaderboard tab switching by removing scope and period from the useEffect dependency array. The fix is correct and resolves issue #177.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 0 issues
  • Medium (🟡): 1 issue
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Medium Priority: Consider adding URL synchronization to tab click handlers so that tab selections are reflected in the URL and persist across page refreshes. This would enable true deep linking and improve UX with browser back/forward navigation. However, this is an enhancement and not blocking for merge.

💡 General Observations

The fix correctly addresses the root cause of the infinite loop by breaking the circular dependency between state updates and effect re-execution. The effect closure pattern used here is safe because it captures current state values when searchParams changes, not stale values from mount time.

Recommendation: ✅ Approve for merge - The fix correctly resolves the blocking bug. The URL synchronization enhancement mentioned above can be addressed in a follow-up PR if desired.


🤖 Automated review by Claude AI - focused on identifying issues for improvement

This was referenced Nov 22, 2025
@ding113 ding113 deleted the fix/issue-177-leaderboard-tab-switching branch November 25, 2025 17:44
@ding113 ding113 mentioned this pull request Nov 28, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size/XS Extra Small PR (< 50 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments