Skip to content

refactor: remove derived-state useEffect/useMemo patterns#830

Open
notJoon wants to merge 4 commits intodevelopfrom
fix/derived-state-no-effect
Open

refactor: remove derived-state useEffect/useMemo patterns#830
notJoon wants to merge 4 commits intodevelopfrom
fix/derived-state-no-effect

Conversation

@notJoon
Copy link
Copy Markdown
Member

@notJoon notJoon commented Feb 25, 2026

Description

When a value is purely derived from props or other state, synchronizing it through useState + useEffect causes two render cycles per change.

Changes

  • useDecreaseHandle where selectedPosition was duplicated in the useEffect deps, which could mask stale closure issues
  • Replace useState + useEffect sync pattern
  • Remove trivial useMemo wrappers where the computation is a simple property access, boolean check, or constant
  • Remove trivial useMemo identity wrapper in useSelectPool (poolPath that just returned latestPoolPath)

Summary by CodeRabbit

  • Tests

    • Added comprehensive test suites for referral, pool position management, and token input hooks, covering edge cases and expected behaviors.
  • Refactor

    • Simplified multiple hooks and components by removing redundant memoization and state indirections, preserving behavior while improving readability and performance.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 25, 2026

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

Project Deployment Actions Updated (UTC)
gnoswap-interface Ready Ready Preview, Comment Feb 25, 2026 6:41am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 25, 2026

Walkthrough

Removes unnecessary useMemo wrappers across several hooks and components, converts token balance state to a memoized computation, and adds comprehensive test suites for four hooks. Changes preserve external behavior and exported signatures.

Changes

Cohort / File(s) Summary
Hook Simplifications (useMemo removals)
packages/web/src/components/common/line-graph/LineGraph.tsx, packages/web/src/hooks/common/use-referral.tsx, packages/web/src/hooks/pool/data/use-decrease-handle.tsx, packages/web/src/hooks/pool/data/use-increase-handle.tsx, packages/web/src/hooks/pool/data/use-select-pool.tsx, packages/web/src/layouts/pool/.../StakingContentCard.tsx
Removed useMemo wrappers for trivial constants/booleans (baseLineCount, referralEarnedPoints, loading, inRange, poolPath, checkedStep) and replaced them with direct expressions or values. No public API or behavior changes.
Balance Computation Refactor
packages/web/src/hooks/token/data/use-token-amount-input.tsx
Replaced stateful balance + effect with a memoized computed balance derived from displayBalanceMap and token; removed useEffect import/usage. Observable behavior unchanged.
New/Updated Tests
packages/web/src/hooks/common/use-referral.spec.tsx, packages/web/src/hooks/pool/data/use-decrease-handle.spec.tsx, packages/web/src/hooks/pool/data/use-increase-handle.spec.tsx, packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx
Adds comprehensive test suites that mock external dependencies and assert derived values: referral points parsing, pool loading/rangeStatus logic, and token balance/amount behaviors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: remove derived-state useEffect/useMemo patterns' accurately and specifically describes the main change: eliminating unnecessary useMemo/useEffect patterns for derived state.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/derived-state-no-effect

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.

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: 3

🧹 Nitpick comments (5)
packages/web/src/layouts/pool/pool-detail/components/staking/staking-content/staking-content-card/StakingContentCard.tsx (1)

114-116: Residual trivial useMemo for periodInfo — same pattern as the removed checkedStep.

Both StakingContentCard (Lines 114–116) and SummuryApr (Lines 291–293) still wrap a plain object property access in useMemo:

const periodInfo = useMemo(() => {
  return STAKING_PERIOD_INFO[period];
}, [period]);

STAKING_PERIOD_INFO[period] is a synchronous constant lookup with no side-effects, making this identical in nature to the checkPoints.includes(period) pattern just removed.

♻️ Proposed simplification (applies to both components)
-  const periodInfo = useMemo(() => {
-    return STAKING_PERIOD_INFO[period];
-  }, [period]);
+  const periodInfo = STAKING_PERIOD_INFO[period];
packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx (1)

27-42: Prefer as unknown as TokenModel over as never for the makeToken helper return type.

never is the TypeScript bottom type (a value that can never exist), so casting a real object to it is semantically inverted. as unknown as TokenModel (or simply typing the return : TokenModel) gives the correct type-narrowing intent and prevents accidental misuse in future tests.

🛠️ Proposed fix
+import { TokenModel } from "@models/token/token-model";
+
-function makeToken(path: string, decimals = 6) {
+function makeToken(path: string, decimals = 6): TokenModel {
   return {
     path,
     ...
-  } as never;
+  } as unknown as TokenModel;
 }
packages/web/src/hooks/pool/data/use-decrease-handle.spec.tsx (1)

107-125: Consider adding inRange/rangeStatus test cases — the refactored logic is currently uncovered.

The inRange computation (use-decrease-handle.tsx Lines 126–129) was refactored from useMemo to a direct boolean in this PR, but no test verifies its IN / OUT / NONE outcomes. The makePosition helper already provides everything needed.

💡 Example cases to add
+  describe("rangeStatus", () => {
+    it("returns IN when currentTick is within [tickLower, tickUpper]", () => {
+      mockSelectedPosition = makePosition(100, 50, 150);
+      const { result } = renderHook(() => useDecreaseHandle(), { wrapper });
+      expect(result.current.rangeStatus).toBe(RANGE_STATUS_OPTION.IN);
+    });
+
+    it("returns OUT when currentTick is below tickLower", () => {
+      mockSelectedPosition = makePosition(30, 50, 150);
+      const { result } = renderHook(() => useDecreaseHandle(), { wrapper });
+      expect(result.current.rangeStatus).toBe(RANGE_STATUS_OPTION.OUT);
+    });
+
+    it("returns NONE when position is closed", () => {
+      mockSelectedPosition = makePosition(100, 50, 150, true);
+      const { result } = renderHook(() => useDecreaseHandle(), { wrapper });
+      expect(result.current.rangeStatus).toBe(RANGE_STATUS_OPTION.NONE);
+    });
+  });
packages/web/src/hooks/common/use-referral.spec.tsx (1)

45-73: Wrap renderHook calls in JotaiProvider and GnoswapThemeProvider.

All five renderHook invocations are missing the required provider wrapper. Even though useReferral's atom-consuming dependencies are mocked here, the codebase-wide rule still applies and guards against test failures if future hooks added to useReferral pull from theme or Jotai context.

♻️ Proposed fix — add a shared wrapper
+import { JotaiProvider } from "@/common/providers/jotai-provider";
+import { GnoswapThemeProvider } from "@/common/providers/gnoswap-theme-provider";
+
+const wrapper = ({ children }: { children: React.ReactNode }) => (
+  <JotaiProvider>
+    <GnoswapThemeProvider>
+      {children}
+    </GnoswapThemeProvider>
+  </JotaiProvider>
+);

 it("returns 0 when leaderboardMyInfo is undefined", () => {
   mockLeaderboardMyInfo = undefined;
-  const { result } = renderHook(() => useReferral());
+  const { result } = renderHook(() => useReferral(), { wrapper });
   expect(result.current.referralEarnedPoints).toBe(0);
 });

Apply the same { wrapper } option to the remaining four renderHook calls.

Based on learnings: "Applies to **/*.spec.{ts,tsx}: Wrap components in JotaiProvider and GnoswapThemeProvider when testing with testing-library/react."

packages/web/src/hooks/pool/data/use-increase-handle.spec.tsx (1)

92-128: Wrap renderHook calls in JotaiProvider and GnoswapThemeProvider.

None of the five renderHook invocations supply a wrapper. While the blanket Jotai mock removes the runtime need for JotaiProvider in these specific tests, the guideline is unconditional for this codebase.

♻️ Proposed fix — add a shared wrapper
+import React from "react";
+import { JotaiProvider } from "@/common/providers/jotai-provider";
+import { GnoswapThemeProvider } from "@/common/providers/gnoswap-theme-provider";
+
+const wrapper = ({ children }: { children: React.ReactNode }) => (
+  <JotaiProvider>
+    <GnoswapThemeProvider>
+      {children}
+    </GnoswapThemeProvider>
+  </JotaiProvider>
+);

 it("returns false when selectedPosition is null", () => {
   mockSelectedPosition = null;
-  const { result } = renderHook(() => useIncreaseHandle());
+  const { result } = renderHook(() => useIncreaseHandle(), { wrapper });
   expect(result.current.loading).toBe(true);
 });

Apply the same { wrapper } option to the remaining four renderHook calls.

Based on learnings: "Applies to **/*.spec.{ts,tsx}: Wrap components in JotaiProvider and GnoswapThemeProvider when testing with testing-library/react."


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89fa8c4 and 917c193.

📒 Files selected for processing (11)
  • packages/web/src/components/common/line-graph/LineGraph.tsx
  • packages/web/src/hooks/common/use-referral.spec.tsx
  • packages/web/src/hooks/common/use-referral.tsx
  • packages/web/src/hooks/pool/data/use-decrease-handle.spec.tsx
  • packages/web/src/hooks/pool/data/use-decrease-handle.tsx
  • packages/web/src/hooks/pool/data/use-increase-handle.spec.tsx
  • packages/web/src/hooks/pool/data/use-increase-handle.tsx
  • packages/web/src/hooks/pool/data/use-select-pool.tsx
  • packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx
  • packages/web/src/hooks/token/data/use-token-amount-input.tsx
  • packages/web/src/layouts/pool/pool-detail/components/staking/staking-content/staking-content-card/StakingContentCard.tsx

Comment thread packages/web/src/hooks/pool/data/use-decrease-handle.spec.tsx
Comment thread packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx
Comment thread packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx
@sonarqubecloud
Copy link
Copy Markdown

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.

♻️ Duplicate comments (1)
packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx (1)

37-52: ⚠️ Potential issue | 🔴 Critical

Remove as never cast and fix type mismatches in makeToken.

The as never cast suppresses type safety and hides actual type errors in the fixture. The object has two problems:

  1. type: 0 should be type: "Native" or type: "GRC20" (TokenModel expects a string, not a number)
  2. isGnot: false does not exist in TokenModel and should be removed

Replace the unsafe cast with an explicit return type derived from the hook signature:

Suggested fix
+type HookToken = Exclude<Parameters<typeof useTokenAmountInput>[0], null>;
+
-function makeToken(path: string, decimals = 6) {
-  return {
+function makeToken(path: string, decimals = 6): HookToken {
+  const token: HookToken = {
     path,
     name: "TestToken",
     symbol: "TT",
     decimals,
     logoURI: "",
     priceID: path,
     chainId: "test",
     createdAt: "",
     address: "",
     wrappedPath: path,
-    type: 0,
-    isGnot: false,
+    type: "Native",
-  } as never;
+  };
+  return token;
 }

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 917c193 and d1d6432.

📒 Files selected for processing (2)
  • packages/web/src/hooks/pool/data/use-decrease-handle.spec.tsx
  • packages/web/src/hooks/token/data/use-token-amount-input.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/hooks/pool/data/use-decrease-handle.spec.tsx

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