Skip to content

Fix: Resolve React hook rule violation in PdfViewer component#1532

Merged
jaseemjaskp merged 1 commit into
mainfrom
fix/pdf-viewer-hook-violation
Sep 15, 2025
Merged

Fix: Resolve React hook rule violation in PdfViewer component#1532
jaseemjaskp merged 1 commit into
mainfrom
fix/pdf-viewer-hook-violation

Conversation

@jaseemjaskp

Copy link
Copy Markdown
Contributor

Problem

The PdfViewer component was showing a React hook rule violation warning in the browser console:

Warning: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks

Solution

  • Moved all highlightPlugin() calls to the component's top level
  • Eliminated conditional plugin creation inside useMemo
  • Created both base and custom highlight plugins unconditionally
  • Used configuration-based approach to determine which plugin to use

Testing

  • No more React hook rule violation warnings in console
  • PDF viewing and highlighting functionality remains intact

Changes

  • frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx: Fixed hook rule violation by restructuring plugin creation

- Move highlightPlugin() calls to top level to avoid calling hooks inside useMemo
- Create both base and custom highlight plugins unconditionally
- Use configuration-based approach to determine which plugin to use
- Eliminates console warning about hook rule violations
@coderabbitai

coderabbitai Bot commented Sep 15, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • More consistent highlight rendering in the PDF viewer, showing either the current highlight or all highlights as appropriate.
  • Bug Fixes
    • Page navigation now reliably jumps to the correct page when highlights change.
    • Prevented rendering of invalid highlight entries for cleaner display.
  • Refactor
    • Internal overhaul of highlight rendering logic to improve stability and responsiveness.

Walkthrough

Reworked PdfViewer highlight rendering: introduced dual highlight plugin instances and conditional selection based on computed currentHighlightData; added data-cleaning for highlight data; adjusted current highlight selection logic; updated page jump effect dependencies; passed the selected plugin instance to the viewer.

Changes

Cohort / File(s) Summary
PDF Viewer highlight rendering
frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx
Split single memoized highlight plugin into base and custom plugins created unconditionally; added removeZerosAndDeleteIfAllZero and processed highlight computation; introduced currentHighlightData selection; custom plugin overrides renderHighlights using RenderHighlights; conditional plugin selection; updated page jump effect dependencies; minor formatting change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant PV as PdfViewer
  participant HPb as baseHighlightPlugin
  participant HPc as customHighlightPlugin
  participant RH as RenderHighlights
  participant VW as Viewer

  U->>PV: Load with highlightData/currentHighlightIndex
  PV->>PV: removeZerosAndDeleteIfAllZero(highlightData)
  PV->>PV: Compute processedHighlightData
  PV->>PV: Derive currentHighlightData (single or all, else null)

  alt currentHighlightData exists
    PV->>HPc: Use custom plugin (renderHighlights override)
    HPc->>RH: render using currentHighlightData
    RH-->>VW: Highlight layers
  else
    PV->>HPb: Use base plugin (default rendering)
    HPb-->>VW: Default highlight rendering
  end

  PV->>PV: useEffect([highlightData, jumpToPage, currentHighlightIndex])
  PV->>VW: Jump to page (if applicable)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description documents the problem, solution, basic testing, and the changed file, so it partially covers the template's intent. However it does not follow the repository's required template headings and is missing several required items—most importantly the "Can this PR break any existing features" section that admins require before merge, plus Database Migrations, Env Config, Related Issues/PRs, Dependencies Versions, and the Checklist. Because required template fields are missing, this check fails. Please update the PR description to match the repository template: add explicit "What", "Why", and "How" headings (or map the existing content to them), fill the "Can this PR break any existing features" field with yes/no and details or justification, and populate Database Migrations, Env Config, Related Issues/PRs, Dependencies Versions, Notes on Testing, and the Checklist confirming the Contribution Guidelines were read; include any relevant screenshots or links to related issues to help reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title concisely and accurately describes the primary change: resolving a React hook rule violation in the PdfViewer component. It matches the changeset which moves highlightPlugin calls to the component top level and restructures plugin creation in PdfViewer.jsx. The phrasing is specific and suitable for scanning PR history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pdf-viewer-hook-violation

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (7)
frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx (7)

41-53: Harden index checks in currentHighlightData.

Guard against undefined/NaN/negative indices to prevent out-of-bounds access.

Apply this diff:

   const currentHighlightData = useMemo(() => {
     if (
       RenderHighlights &&
       Array.isArray(processedHighlightData) &&
       processedHighlightData?.length > 0
     ) {
-      return currentHighlightIndex !== null &&
-        currentHighlightIndex < processedHighlightData.length
-        ? [processedHighlightData[currentHighlightIndex]]
-        : processedHighlightData;
+      const hasValidIndex =
+        Number.isInteger(currentHighlightIndex) &&
+        currentHighlightIndex >= 0 &&
+        currentHighlightIndex < processedHighlightData.length;
+      return hasValidIndex
+        ? [processedHighlightData[currentHighlightIndex]]
+        : processedHighlightData;
     }
     return null;
   }, [processedHighlightData, currentHighlightIndex]);

19-22: Memoize plugin instances to stop effect churn and re-init costs.

pageNavigationPlugin() and defaultLayoutPlugin() are recreated every render, changing jumpToPage identity and re-running the effect unnecessarily.

Apply these diffs:

-  const newPlugin = defaultLayoutPlugin();
-  const pageNavigationPluginInstance = pageNavigationPlugin();
+  const defaultLayoutPluginInstance = useMemo(() => defaultLayoutPlugin(), []);
+  const pageNavigationPluginInstance = useMemo(() => pageNavigationPlugin(), []);
   const { jumpToPage } = pageNavigationPluginInstance;
           plugins={[
-            newPlugin,
+            defaultLayoutPluginInstance,
             pageNavigationPluginInstance,
             highlightPluginInstance,
           ]}

Also applies to: 97-100


55-65: Only recreate highlight plugins when needed.

Re-instantiating on every render can reset plugin internal state. Memoize; keep custom plugin reactive to data.

Apply this diff:

-  const baseHighlightPlugin = highlightPlugin();
-  const customHighlightPlugin = highlightPlugin({
-    renderHighlights:
-      currentHighlightData && RenderHighlights
-        ? (props) => (
-            <RenderHighlights {...props} highlightData={currentHighlightData} />
-          )
-        : undefined,
-  });
+  const baseHighlightPlugin = useMemo(() => highlightPlugin(), []);
+  const customHighlightPlugin = useMemo(
+    () =>
+      highlightPlugin({
+        renderHighlights:
+          currentHighlightData && RenderHighlights
+            ? (props) => (
+                <RenderHighlights
+                  {...props}
+                  highlightData={currentHighlightData}
+                />
+              )
+            : undefined,
+      }),
+    [RenderHighlights, currentHighlightData]
+  );

76-87: Clamp index and clean up the timer.

Prevents negative/NaN index and eliminates dangling timeout on rapid re-renders/unmount.

Apply this diff:

-      const index =
-        currentHighlightIndex !== null &&
-        currentHighlightIndex < cleanedHighlightData.length
-          ? currentHighlightIndex
-          : 0;
+      const index =
+        Number.isInteger(currentHighlightIndex) &&
+        currentHighlightIndex >= 0 &&
+        currentHighlightIndex < cleanedHighlightData.length
+          ? currentHighlightIndex
+          : 0;
       const pageNumber = cleanedHighlightData[index][0]; // Get page number from current highlight

       if (pageNumber !== null && pageNumber !== undefined && jumpToPage) {
-        setTimeout(() => {
-          jumpToPage(pageNumber); // jumpToPage is 0-indexed
-        }, 100); // Add a slight delay to ensure proper page rendering
+        const t = setTimeout(() => {
+          jumpToPage(pageNumber); // jumpToPage is 0-indexed
+        }, 100); // Slight delay to ensure proper page rendering
+        return () => clearTimeout(t);
       }

108-111: Tighten PropTypes for clarity.

Clarifies accepted shapes and helps catch bad inputs early.

Apply this diff:

-PdfViewer.propTypes = {
-  fileUrl: PropTypes.any,
-  highlightData: PropTypes.array,
-  currentHighlightIndex: PropTypes.number,
-};
+PdfViewer.propTypes = {
+  fileUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+  // Expect array of [page, x, y, h] (or similar numeric tuples)
+  highlightData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
+  currentHighlightIndex: PropTypes.number, // pass undefined/null when not focusing a single highlight
+};

19-19: Naming nit: prefer descriptive variable name.

Rename newPlugin → defaultLayoutPluginInstance for clarity.

(Handled by the memoization diff above.)


10-16: Optional: avoid require in ESM setups.

If the app uses ESM/Vite, require may be undefined. Consider a static import with feature flag, or a guarded dynamic import with state.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between eac6ebb and f3545ee.

📒 Files selected for processing (1)
  • frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx (2)

55-69: Hook-order fix looks good.

Creating both highlight plugins at the component top level removes the hook rule violation as intended.


93-93: Verify pdfjs-dist worker version matches installed deps; consider self-hosting.

package.json was not available in the verification environment; cannot confirm installed pdfjs-dist. Ensure the hardcoded worker URL (https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js) matches the pdfjs-dist version in your package.json — if not, update the workerUrl to the installed version or serve the worker from your app’s static assets.

Location: frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx (line 93).

Run locally to check: jq -r '.dependencies["pdfjs-dist"] // .devDependencies["pdfjs-dist"] // "not found"' package.json

Comment thread frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx
@jaseemjaskp jaseemjaskp merged commit 0e15ce3 into main Sep 15, 2025
6 checks passed
@jaseemjaskp jaseemjaskp deleted the fix/pdf-viewer-hook-violation branch September 15, 2025 10:51
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.

3 participants