Skip to content

Feat react compiler migration - #4065

Merged
amitsingh-007 merged 3 commits into
mainfrom
feat-react-compiler-migration
Jul 11, 2026
Merged

amitsingh-007 merged 3 commits into
mainfrom
feat-react-compiler-migration

Conversation

@amitsingh-007

@amitsingh-007 amitsingh-007 commented Jul 11, 2026 •

Copy link
Copy Markdown
Owner

Check if the Pull Request fulfils these requirements

  • Does the extension require a version change?

Summary by Sourcery

Migrate several React components and hooks to React 19 compiler patterns while updating the extension version and workspace tooling.

New Features:

  • Introduce useEffectEvent-based handlers for history monitoring and debounced search to align with React 19 effect event semantics.

Enhancements:

  • Replace memoized component wrappers with plain function components across shared and extension UI, relying on the new React compiler for rendering optimizations.
  • Adopt the experimental use() API in place of useContext for consuming shared DynamicContext and AuthContext.
  • Make the current tab hook resilient to unmount during async resolution by guarding state updates.
  • Adjust exhaustive-deps behavior via eslint-plugin-react-hooks upgrade to support useEffectEvent in the workspace configuration.
  • Clarify contribution guidelines around code comments in AGENTS.md.

Build:

  • Bump the extension package version from 24.11.0 to 24.12.0.

Greptile Summary

This PR migrates the extension and shared packages to the React Compiler, removing memo() wrappers that are now handled automatically, replacing useContext with the React 19 use() hook, and adopting useEffectEvent for callbacks that are used inside effects but should not be reactive dependencies.

  • All memo() removals are backed by the compiler being configured in both wxt.config.ts (via reactCompilerPreset()) and next.config.ts (reactCompiler: true), so no memoization is lost.
  • useEffectEvent is used correctly: it is only ever called inside useEffect bodies, and the eslint-plugin-react-hooks override to v6.1.1 ensures the exhaustive-deps rule recognizes it instead of treating it as a missing dependency.
  • useCurrentTab gains a standard ignore-flag cleanup pattern, and Person's effect dependency array is correctly trimmed to uid only.

Confidence Score: 5/5

Safe to merge — all memo() removals are backed by the React Compiler being active in both apps, useEffectEvent is used correctly within effect bodies only, and the eslint-plugin override ensures the linter validates the new patterns properly.

The migration is consistent and complete: the compiler is wired up in both the extension (wxt.config.ts with reactCompilerPreset) and the web app (next.config.ts with reactCompiler: true), so dropping memo() is safe. The useEffectEvent usages follow React 19 rules — called only from inside effects, never passed to custom hooks. The use() replacements for synchronous context reads are semantically identical to useContext().

No files require special attention.

Reviews (1): Last reviewed commit: "Update package.json" | Re-trigger Greptile

amitsingh-007 and others added 3 commits July 11, 2026 23:28
Drop manual memoization (memo/useCallback/useMemo) now handled by the
React Compiler, and adopt use() and useEffectEvent across popup and shared
components. Bump eslint-plugin-react-hooks to 6.1.1 (via the xo react config
override) so useEffectEvent is linted correctly under React 19.2, and add a
comment-style guideline to AGENTS.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@webext-bot

webext-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

Extension version is updated from 24.11.0 to 24.12.0

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • You’ve removed a number of memo wrappers (e.g. Person, BookmarkRow, BookmarksHeader, Folder); given these components appear in lists/virtualized views, it’s worth double-checking that the extra re-renders don’t regress performance in those UIs, and reintroducing memoization selectively if needed.
  • The new useEffectEvent usages (e.g. in ToggleHistory and Search) change how closures and dependencies behave; consider adding a brief inline comment near the first use to clarify why it’s used instead of a standard effect, to reduce future confusion when others maintain or refactor these hooks.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You’ve removed a number of `memo` wrappers (e.g. `Person`, `BookmarkRow`, `BookmarksHeader`, `Folder`); given these components appear in lists/virtualized views, it’s worth double-checking that the extra re-renders don’t regress performance in those UIs, and reintroducing memoization selectively if needed.
- The new `useEffectEvent` usages (e.g. in `ToggleHistory` and `Search`) change how closures and dependencies behave; consider adding a brief inline comment near the first use to clarify why it’s used instead of a standard effect, to reduce future confusion when others maintain or refactor these hooks.

## Individual Comments

### Comment 1
<location path="packages/shared/src/components/Bookmarks/components/Folder.tsx" line_range="28-33" />
<code_context>
+  };

-    return (
-      <div
-        className={cn(
-          'flex size-full items-center justify-center gap-3 p-1.5',
-          isEmpty && 'cursor-not-allowed opacity-60'
-        )}
-        data-testid={`folder-item-${origName}`}
-        onClick={resetSelectedBookmarks}
-        onDoubleClick={handleFolderOpen}
</code_context>
<issue_to_address>
**suggestion (testing):** Using `origName` directly in `data-testid` may lead to unstable selectors if names contain spaces or are user-editable.

Embedding `origName` in the `data-testid` makes tests depend on a user-controlled, potentially changing string (whitespace, special chars, renamed folders), which can make selectors fragile and harder to maintain. If there’s a stable identifier available (e.g. `id`), consider basing the test id on that instead.

Suggested implementation:

```typescript
        data-testid={`folder-item-${id}`}

```

1. Update any component/unit tests that currently query by `data-testid="folder-item-${origName}"` to use `folder-item-${id}` instead.
2. If `id` is not guaranteed to be present for all folders, ensure that the component receives a stable identifier prop (e.g. `id`) for every rendered folder item and use that consistently for the test id.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/shared/src/components/Bookmarks/components/Folder.tsx
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Migrate UI components to React Compiler patterns and update hooks linting

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Migrate popup/shared/web components to React Compiler patterns (drop memoization, adopt
 use/useEffectEvent).
• Tighten effect safety to avoid stale closures and async state updates after unmount.
• Pin eslint-plugin-react-hooks v6.1.1 via workspace overrides and bump extension version.
Diagram

graph TD
  R[["React 19.2 / Compiler"]] --> P["Extension popup UI"] --> S["Shared UI components"] --> C[("DynamicContext")]
  R --> W["Web AuthProvider"]
  O["Workspace overrides"] --> E["eslint-plugin-react-hooks 6.1.1"]
  O --> L["pnpm lockfile"]
  subgraph Legend
    direction LR
    _lib[["Library/Runtime"]] ~~~ _cmp["UI Component"] ~~~ _cfg["Config"] ~~~ _ctx[("Context")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep memo/useCallback/useMemo until React Compiler rollout is complete
  • ➕ Lower behavioral risk if compiler assumptions change
  • ➕ Avoids relying on new hook semantics (use/useEffectEvent) everywhere at once
  • ➖ More boilerplate and harder-to-maintain dependency arrays
  • ➖ May conflict with or reduce the benefit of compiler optimizations
2. Introduce an app-level lint config bump instead of pnpm override
  • ➕ More explicit ownership at the consuming package (less global behavior)
  • ➕ Reduces risk of other workspace packages being affected by overrides
  • ➖ May not work if eslint-config-xo-react hard-pins an incompatible version
  • ➖ Still requires coordinating ESLint resolution across workspace
3. Codemod-driven migration with targeted performance tests
  • ➕ More uniform changes and fewer manual mistakes
  • ➕ Can validate performance regressions in hot paths (virtual lists, headers)
  • ➖ Higher upfront setup cost
  • ➖ May be overkill if this is a one-time migration

Recommendation: The PR’s approach (remove manual memoization and adopt use()/useEffectEvent, plus pinning hooks linting via an override) is appropriate for a React Compiler migration. The pnpm override is justified given the eslint-config-xo-react constraint and the documented rationale; reviewers should focus on behavioral correctness of effects/handlers and any subtle dependency changes rather than performance micro-optimizations.

Files changed (20) +207 / -193

Enhancement (1) +2 / -2
AuthProvider.tsxUse React use() for AuthContext consumption +2/-2

Use React use() for AuthContext consumption

• Replaces useContext(AuthContext) with use(AuthContext) in the useUser hook. This aligns the provider with React 19+ context consumption patterns.

apps/web/src/app/provider/AuthProvider.tsx

Bug fix (2) +12 / -6
useCurrentTab.tsPrevent setState after unmount in useCurrentTab +7/-1

Prevent setState after unmount in useCurrentTab

• Adds an ignore flag and cleanup to avoid setting state after the effect is cleaned up. This reduces potential warnings/races when the popup unmounts while getCurrentTab resolves.

apps/extension/src/entrypoints/popup/hooks/useCurrentTab.ts

Person.tsxRemove memo and fix effect dependencies while adopting use() +5/-5

Remove memo and fix effect dependencies while adopting use()

• Converts the component to a function component, uses use(DynamicContext), and tightens the image-resolve effect dependency list to avoid unnecessary reruns. Improves correctness by not depending on the whole person object.

packages/shared/src/components/Persons/components/Person.tsx

Refactor (13) +169 / -179
BookmarkRow.tsxDrop memo wrapper from BookmarkRow for React Compiler +18/-19

Drop memo wrapper from BookmarkRow for React Compiler

• Replaces React.memo usage with a plain function component. Keeps behavior the same while relying on compiler-driven memoization and simplifying component structure.

apps/extension/src/entrypoints/popup/panels/BookmarksPanel/components/BookmarkRow.tsx

BookmarksHeader.tsxConvert BookmarksHeader from memoized component to function +2/-3

Convert BookmarksHeader from memoized component to function

• Removes React.memo wrapping and exports a standard function component. Intended to align with React Compiler patterns without changing UI behavior.

apps/extension/src/entrypoints/popup/panels/BookmarksPanel/components/BookmarksHeader.tsx

ConfirmationDialog.tsxRemove memoization from ConfirmationDialog +2/-3

Remove memoization from ConfirmationDialog

• Drops React.memo to simplify the component and rely on React Compiler optimizations. Render logic and props remain unchanged.

apps/extension/src/entrypoints/popup/panels/BookmarksPanel/components/ConfirmationDialog.tsx

ToggleHistory.tsxAdopt useEffectEvent for history toggle side effects +11/-9

Adopt useEffectEvent for history toggle side effects

• Replaces useCallback-managed handlers with plain functions wrapped by useEffectEvent for stable effect callbacks. Simplifies effect dependency arrays while keeping toggle behavior consistent.

apps/extension/src/entrypoints/popup/panels/HomePopup/components/ToggleHistory.tsx

PersonHeader.tsxRemove memoization from PersonHeader and keep dialog flow intact +43/-46

Remove memoization from PersonHeader and keep dialog flow intact

• Converts the component from a memoized wrapper to a standard function component. Preserves add-person dialog behavior and header actions while aligning with compiler patterns.

apps/extension/src/entrypoints/popup/panels/PersonsPanel/components/PersonHeader.tsx

Folder.tsxConvert Folder to function component and use() DynamicContext +30/-27

Convert Folder to function component and use() DynamicContext

• Removes memo/useContext and switches to use(DynamicContext) for location access. Keeps folder open behavior and click handlers unchanged.

packages/shared/src/components/Bookmarks/components/Folder.tsx

PersonAvatars.tsxRemove memo/useContext; switch to use() for navigation context +4/-4

Remove memo/useContext; switch to use() for navigation context

• Migrates DynamicContext access to use() and drops React.memo. Preserves empty-state rendering and avatar click navigation.

packages/shared/src/components/Bookmarks/components/PersonAvatars.tsx

Header.tsxUse use() for DynamicContext and remove memo wrapper +45/-48

Use use() for DynamicContext and remove memo wrapper

• Replaces memoized component with a function component and switches context consumption to use(). Back button behavior continues to default to location.goBack when no handler is provided.

packages/shared/src/components/Header.tsx

BookmarksList.tsxSwitch DynamicContext consumption to use() in BookmarksList +2/-2

Switch DynamicContext consumption to use() in BookmarksList

• Updates context access from useContext to use() while leaving the rest of the bookmark list logic intact. Maintains existing callback usage for list interactions.

packages/shared/src/components/Persons/components/BookmarksList.tsx

Persons.tsxAdopt use() for DynamicContext in Persons list +2/-8

Adopt use() for DynamicContext in Persons list

• Switches from useContext to use() for accessing location/query string. Keeps virtualization and scrolling behavior intact.

packages/shared/src/components/Persons/components/Persons.tsx

ScrollButton.tsxRemove memo from ScrollButton export +2/-3

Remove memo from ScrollButton export

• Exports ScrollButton as a plain function rather than a memoized component. Render behavior is unchanged, but aligns with compiler-first patterns.

packages/shared/src/components/ScrollButton.tsx

Search.tsxUse useEffectEvent for debounced Search onChange +6/-5

Use useEffectEvent for debounced Search onChange

• Removes memo and wraps onChange with useEffectEvent to avoid stale closures and dependency churn. Effect now depends only on the debounced value while still dispatching updates.

packages/shared/src/components/Search.tsx

useStorage.tsUse use() to read storage from DynamicContext +2/-2

Use use() to read storage from DynamicContext

• Replaces useContext with use() when retrieving the storage client from DynamicContext. Leaves storage API methods and useCallback usage intact.

packages/shared/src/hooks/useStorage.ts

Documentation (1) +1 / -0
AGENTS.mdAdd guidance to keep comments short and intent-focused +1/-0

Add guidance to keep comments short and intent-focused

• Adds a style guideline encouraging minimal comments that explain the why rather than restating code. This aligns documentation with expected code review standards during the migration.

AGENTS.md

Other (3) +23 / -6
package.jsonBump extension version to 24.12.0 +1/-1

Bump extension version to 24.12.0

• Updates the extension package version to reflect the migration-related changes. No script or dependency changes are shown in this diff snippet beyond the version bump.

apps/extension/package.json

pnpm-lock.yamlLock eslint-plugin-react-hooks 6.1.1 and new transitive deps +18/-5

Lock eslint-plugin-react-hooks 6.1.1 and new transitive deps

• Updates the lockfile to reflect the eslint-plugin-react-hooks 6.1.1 resolution via override. Records new transitive dependencies pulled by v6 (e.g., zod-validation-error) and updated engines metadata.

pnpm-lock.yaml

pnpm-workspace.yamlAdd workspace override for eslint-plugin-react-hooks 6.1.1 with rationale +4/-0

Add workspace override for eslint-plugin-react-hooks 6.1.1 with rationale

• Pins eslint-plugin-react-hooks to 6.1.1 specifically for eslint-config-xo-react, with comments explaining the false-positive exhaustive-deps behavior for useEffectEvent in older versions. Keeps the existing jwks-rsa>jose override.

pnpm-workspace.yaml

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request adds an unmount guard to current-tab loading, uses effect events for history and search effects, and switches context reads to React’s use hook. Several extension and shared components are converted from memo-wrapped declarations to plain function components. It also updates the hooks lint-plugin override, extension version, and agent comment guidance.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: a React compiler migration.
Description check ✅ Passed The description satisfies the template by explicitly answering the required version-change checkbox.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-react-compiler-migration

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pnpm-workspace.yaml`:
- Around line 11-14: Update the eslint-config-xo-react override in
pnpm-workspace.yaml from eslint-plugin-react-hooks 6.1.1 to the latest stable
7.1.1, after confirming compatibility with eslint-config-xo-react. Preserve the
existing override and rationale for correcting useEffectEvent dependency
linting.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e56a494e-d8e9-4c65-ac1c-a18df9412ec0

📥 Commits

Reviewing files that changed from the base of the PR and between 620de15 and 2b849ce.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • AGENTS.md
  • apps/extension/package.json
  • apps/extension/src/entrypoints/popup/hooks/useCurrentTab.ts
  • apps/extension/src/entrypoints/popup/panels/BookmarksPanel/components/BookmarkRow.tsx
  • apps/extension/src/entrypoints/popup/panels/BookmarksPanel/components/BookmarksHeader.tsx
  • apps/extension/src/entrypoints/popup/panels/BookmarksPanel/components/ConfirmationDialog.tsx
  • apps/extension/src/entrypoints/popup/panels/HomePopup/components/ToggleHistory.tsx
  • apps/extension/src/entrypoints/popup/panels/PersonsPanel/components/PersonHeader.tsx
  • apps/web/src/app/provider/AuthProvider.tsx
  • packages/shared/src/components/Bookmarks/components/Folder.tsx
  • packages/shared/src/components/Bookmarks/components/PersonAvatars.tsx
  • packages/shared/src/components/Header.tsx
  • packages/shared/src/components/Persons/components/BookmarksList.tsx
  • packages/shared/src/components/Persons/components/Person.tsx
  • packages/shared/src/components/Persons/components/Persons.tsx
  • packages/shared/src/components/ScrollButton.tsx
  • packages/shared/src/components/Search.tsx
  • packages/shared/src/hooks/useStorage.ts
  • pnpm-workspace.yaml

Comment thread pnpm-workspace.yaml
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 22 rules

Grey Divider


Action required

1. History toggle race leak 🐞 Bug ≡ Correctness
Description
ToggleHistory.turnOffHistory only calls endHistoryWatch() when isHistoryActive is true, and
the extension-off effect no longer re-triggers when isHistoryActive flips after the async
turnOnHistory() completes. If the extension is disabled while startHistoryWatch() is in-flight,
historyStartTimeItem can be set without cleanup, leaving history tracking state active while the
extension is off.
Code

apps/extension/src/entrypoints/popup/panels/HomePopup/components/ToggleHistory.tsx[R33-51]

+  const turnOffHistory = () => {
    if (isHistoryActive) {
      endHistoryWatch();
      setIsHistoryActive(false);
    }
-  }, [isHistoryActive]);
+  };
+  const onExtensionInactive = useEffectEvent(turnOffHistory);

-  const turnOnHistory = useCallback(async () => {
+  const turnOnHistory = async () => {
    if (!isHistoryActive) {
      resetHistoryMonitor();
      await startHistoryWatch();
      setIsHistoryActive(true);
    }
-  }, [isHistoryActive, resetHistoryMonitor]);
+  };
+  const onMonitorHistory = useEffectEvent(turnOnHistory);

  // Init toggle on mount
  useEffect(() => {
Relevance

⭐⭐⭐ High

Team previously accepted async cleanup/race-leak fixes (AuthProvider unsubscribe race; test fixtures
try/finally cleanup).

PR-#3908
PR-#3948

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
startHistoryWatch() persists the start time in historyStartTimeItem, but the disable path only
clears history when the local isHistoryActive state is already true; since turnOnHistory() sets
that state only after awaiting startHistoryWatch(), disabling the extension during that await can
skip cleanup and leave the start time stored.

apps/extension/src/entrypoints/popup/panels/HomePopup/components/ToggleHistory.tsx[33-69]
apps/extension/src/utils/history.ts[5-15]
apps/extension/src/entrypoints/popup/panels/HomePopup/components/ToggleHistory.tsx[8-23]
apps/extension/src/storage/items.ts[61-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ToggleHistory` can leave `historyStartTimeItem` set when the extension becomes inactive during the async `turnOnHistory()` path, because `turnOffHistory()` is gated on `isHistoryActive` (which may still be `false` when the disable effect runs).

## Issue Context
- `startHistoryWatch()` writes the start timestamp to `historyStartTimeItem`.
- `endHistoryWatch()` already safely no-ops when no start timestamp exists.

## Fix approach
- Remove the `if (isHistoryActive)` guard (or replace it with a storage check) so disable always attempts cleanup.
- Optionally `await endHistoryWatch()` to avoid overlapping clear operations.
- Alternatively/additionally, after `await startHistoryWatch()` finishes, check `isExtensionActive` and immediately clean up if it’s now inactive.

## Fix Focus Areas
- apps/extension/src/entrypoints/popup/panels/HomePopup/components/ToggleHistory.tsx[33-76]
- apps/extension/src/utils/history.ts[5-15]
- apps/extension/src/storage/items.ts[61-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@amitsingh-007
amitsingh-007 merged commit 23266b9 into main Jul 11, 2026
7 checks passed
@amitsingh-007
amitsingh-007 deleted the feat-react-compiler-migration branch July 11, 2026 18:29
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