Skip to content

🧹 [code health] Refactor useCardSettings to remove redundant effect and eslint-disable#376

Open
is0692vs wants to merge 1 commit into
mainfrom
fix/use-card-settings-eslint-disable-1774850177054424438
Open

🧹 [code health] Refactor useCardSettings to remove redundant effect and eslint-disable#376
is0692vs wants to merge 1 commit into
mainfrom
fix/use-card-settings-eslint-disable-1774850177054424438

Conversation

@is0692vs

@is0692vs is0692vs commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🎯 What: Removed the isHydrated state, the redundant useEffect that was updating state from local storage on mount, and the associated eslint-disable react-hooks/set-state-in-effect comments in src/hooks/useCardSettings.ts.

💡 Why: Setting state synchronously inside an effect is discouraged in React as it causes cascading renders. Since this hook is only consumed by CardGeneratorModal (which handles hydration safety by returning null when not mounted), it is perfectly safe to lazily initialize the useState directly from local storage using loadCardSettings(). This cleans up the code, removes the need for ESLint disable comments, and avoids an unnecessary extra render.

Verification: Verified by checking the implementation logic, running npm run lint (which successfully passed without any custom disable rules), running the complete Vitest test suite (npm run test), and performing a production build (npm run build). All tests passed, and no new issues were introduced.

Result: Improved readability and adherence to React best practices by removing the redundant hydration state synchronization effect and suppressing directives.


PR created automatically by Jules for task 1774850177054424438 started by @is0692vs

Greptile Summary

このPRは useCardSettings フックから isHydrated ステートと初期化用 useEffect、および関連する eslint-disable コメントを削除し、useState の遅延初期化(lazy initializer)に一本化したリファクタリングです。

  • loadCardSettings() を lazy initializer として直接利用することで、エフェクト経由の二重ロードを排除し、不要な再レンダーを回避しています。
  • CardGeneratorModal!mounted 時に null を返すため、SSR/ハイドレーションの不整合リスクはなく、変更は安全です。

Confidence Score: 4/5

マージしても安全な変更です。ロジックの正確性は担保されており、SSR/ハイドレーション上の懸念もありません。

削除されたエフェクトは lazy initializer で完全に代替でき、loadCardSettings() の window チェックにより SSR でも安全に動作します。CardGeneratorModal が mounted=false 時に null を返すことで DOM の不整合も発生しません。唯一の指摘は loadCardSettings() を2回呼んでいる軽微なスタイル上の問題のみです。

特に注意が必要なファイルはありません。

Important Files Changed

Filename Overview
src/hooks/useCardSettings.ts isHydrated ステートと初期化 useEffect を削除し、useState の遅延初期化に統一。loadCardSettings() が2回別々に呼ばれる軽微な重複あり。

Sequence Diagram

sequenceDiagram
    participant Browser
    participant CardGeneratorModal
    participant useCardSettings
    participant localStorage

    Browser->>CardGeneratorModal: "初回レンダー (mounted=false)"
    CardGeneratorModal->>useCardSettings: useCardSettings(false)
    useCardSettings->>localStorage: loadCardSettings() × 2(lazy initializer)
    localStorage-->>useCardSettings: layout / options
    CardGeneratorModal-->>Browser: return null(マウント待ち)

    Note over Browser: setTimeout(0) 完了

    Browser->>CardGeneratorModal: "再レンダー (mounted=true)"
    CardGeneratorModal->>useCardSettings: useCardSettings(true)
    useCardSettings->>localStorage: saveCardSettings(layout, options)(persist effect)
    CardGeneratorModal-->>Browser: モーダル UI を描画
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/hooks/useCardSettings.ts:13-16
`loadCardSettings()` が2つの `useState` 遅延初期化で個別に呼ばれており、localStorage への読み取りが2回発生します。1回の呼び出しに統一することで、I/Oを減らしつつコードの意図も明確になります。

```suggestion
  const [layout, setLayout] = useState<CardLayout>(() => {
    const { layout } = loadCardSettings();
    return layout;
  });
  const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(() => {
    const { options } = loadCardSettings();
    return options;
  });
```

Reviews (1): Last reviewed commit: "refactor: Remove redundant useEffect and..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

…dSettings

Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
github-user-summary Ignored Ignored Jun 12, 2026 7:03am

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@is0692vs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 35 minutes and 40 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc799086-9dd1-4973-ae18-8ff1e0a66618

📥 Commits

Reviewing files that changed from the base of the PR and between a475ecb and a07b3d0.

📒 Files selected for processing (1)
  • src/hooks/useCardSettings.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/use-card-settings-eslint-disable-1774850177054424438

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.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request simplifies the useCardSettings hook by removing the isHydrated state and its associated initialization effect. However, this change introduces a critical bug where user settings in localStorage are overwritten with default values upon mounting because the hydration guard was removed. It is recommended to restore the hydration check to prevent data loss.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 13 to +25
const [layout, setLayout] = useState<CardLayout>(() => loadCardSettings().layout);
const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(
() => loadCardSettings().options,
);

// Initialize state from storage on mount
useEffect(() => {
if (!mounted || isHydrated) {
return;
}

const { layout: storedLayout, options: storedOptions } = loadCardSettings();

// eslint-disable-next-line react-hooks/set-state-in-effect
setLayout((prev) => JSON.stringify(prev) !== JSON.stringify(storedLayout) ? storedLayout : prev);
// eslint-disable-next-line react-hooks/set-state-in-effect
setDisplayOptions((prev) => JSON.stringify(prev) !== JSON.stringify(storedOptions) ? storedOptions : prev);

setIsHydrated(true);
}, [mounted, isHydrated]);

// Persist changes to storage
useEffect(() => {
if (!mounted || !isHydrated) {
if (!mounted) {
return;
}

saveCardSettings(layout, displayOptions);
}, [layout, displayOptions, mounted, isHydrated]);
}, [layout, displayOptions, mounted]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

🚨 Critical Bug: User settings will be wiped out on every page load (SSR/Hydration issue)

While removing the isHydrated state and the useEffect seems like a clean refactoring, it introduces a critical bug that completely overwrites and wipes out the user's saved settings in localStorage with the default settings on every page load.

Why this happens:

  1. Rules of Hooks: In any parent component (like CardGeneratorModal), hooks must be called at the top level, before any early returns (such as if (!mounted) return null;).
  2. Server-Side Rendering (SSR): During SSR, mounted is false. useCardSettings(false) is executed on the server. Since window is undefined, loadCardSettings() returns the default settings, and the state (layout and displayOptions) is initialized to these defaults.
  3. Client-Side Hydration: During hydration, mounted is still false. The state remains initialized to the default values.
  4. Mounting: Once the component mounts, mounted becomes true.
  5. The Bug: The persistence useEffect triggers because mounted changed to true. Since the isHydrated guard was removed, the effect immediately calls saveCardSettings(layout, displayOptions) using the current state values—which are the default values!
  6. Data Loss: This immediately overwrites the user's custom settings in localStorage with the defaults.

Solution:

To prevent this, we must keep the isHydrated guard (or a similar mechanism) to ensure we never write to localStorage until we have successfully loaded the stored settings on the client side after mounting.

  const [layout, setLayout] = useState<CardLayout>(() => loadCardSettings().layout);
  const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(
    () => loadCardSettings().options,
  );
  const [isHydrated, setIsHydrated] = useState(false);

  // Initialize state from storage on mount
  useEffect(() => {
    if (!mounted || isHydrated) {
      return;
    }

    const { layout: storedLayout, options: storedOptions } = loadCardSettings();
    setLayout(storedLayout);
    setDisplayOptions(storedOptions);
    setIsHydrated(true);
  }, [mounted, isHydrated]);

  // Persist changes to storage
  useEffect(() => {
    if (!mounted || !isHydrated) {
      return;
    }

    saveCardSettings(layout, displayOptions);
  }, [layout, displayOptions, mounted, isHydrated]);

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment on lines 13 to 16
const [layout, setLayout] = useState<CardLayout>(() => loadCardSettings().layout);
const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(
() => loadCardSettings().options,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 loadCardSettings() が2つの useState 遅延初期化で個別に呼ばれており、localStorage への読み取りが2回発生します。1回の呼び出しに統一することで、I/Oを減らしつつコードの意図も明確になります。

Suggested change
const [layout, setLayout] = useState<CardLayout>(() => loadCardSettings().layout);
const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(
() => loadCardSettings().options,
);
const [layout, setLayout] = useState<CardLayout>(() => {
const { layout } = loadCardSettings();
return layout;
});
const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(() => {
const { options } = loadCardSettings();
return options;
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/hooks/useCardSettings.ts
Line: 13-16

Comment:
`loadCardSettings()` が2つの `useState` 遅延初期化で個別に呼ばれており、localStorage への読み取りが2回発生します。1回の呼び出しに統一することで、I/Oを減らしつつコードの意図も明確になります。

```suggestion
  const [layout, setLayout] = useState<CardLayout>(() => {
    const { layout } = loadCardSettings();
    return layout;
  });
  const [displayOptions, setDisplayOptions] = useState<CardDisplayOptions>(() => {
    const { options } = loadCardSettings();
    return options;
  });
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant