Fix: Resolve React hook rule violation in PdfViewer component#1532
Conversation
- 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
Summary by CodeRabbit
WalkthroughReworked 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
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
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.
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. Comment |
|
There was a problem hiding this comment.
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
📒 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



Problem
The PdfViewer component was showing a React hook rule violation warning in the browser console:
Solution
highlightPlugin()calls to the component's top leveluseMemoTesting
Changes
frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx: Fixed hook rule violation by restructuring plugin creation