Skip to content

Migrate recharts from v2 to v3#585

Merged
SvenVw merged 5 commits into
developmentfrom
FDM581
Apr 28, 2026
Merged

Migrate recharts from v2 to v3#585
SvenVw merged 5 commits into
developmentfrom
FDM581

Conversation

@BoraIneviNMI
Copy link
Copy Markdown
Collaborator

@BoraIneviNMI BoraIneviNMI commented Apr 21, 2026

Chores

  • Migrated Recharts from v2 to v3 for nitrogen and organic matter balance charts.

Closes #581

Summary by CodeRabbit

  • Style

    • Updated chart shadows, outline and tooltip styling, and color tokens for cleaner visuals
    • Refined tooltip and legend content to exclude placeholder items and improve layout
    • Improved per-bar hover feedback on the nitrogen balance chart
    • Adjusted card container shadow for a more consistent UI
  • Chores

    • Upgraded charting library to a newer major version

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

Updated Recharts to v3 and migrated chart components: tooltip/hover wiring in nitrogen chart, color token syntax in charts, chart utilities adjustments, minor Card style change, and package dependency bump to recharts@^3.8.1. No public API signatures changed.

Changes

Cohort / File(s) Summary
Nitrogen balance chart
fdm-app/app/components/blocks/balance/nitrogen-chart.tsx
Reworked hover/focus model from tooltip-driven to per-bar barOutlineFocus, moved outline rendering into a ZIndexLayer, changed tooltip rendering to use payload directly, added name: "Balans" to chartData, switched <YAxis> to dataKey="name" (removed ticks), and removed an unnecessary cast in addFertilizerApplication.
Organic matter chart (colors)
fdm-app/app/components/blocks/balance/organic-matter-chart.tsx
Replaced color tokens hsl(var(--chart-1))/hsl(var(--chart-2)) with var(--color-chart-1) / var(--color-chart-2) in chartConfig.
Chart utilities / shadcn wrapper
fdm-app/app/components/ui/chart.tsx
Adjusted CSS utility selectors (outline-hiddenoutline-none), changed tooltip container width to min-w-[8rem], filtered out payload items with type === "none" in tooltip and legend rendering, updated indicator color class syntax, and reordered some exports (no API changes).
Card UI
fdm-app/app/components/ui/card.tsx
Changed default shadow class from shadow-sm to shadow and reordered named exports (export order only).
Dependency bump
fdm-app/package.json
Upgraded recharts dependency from ^2.15.4 to ^3.8.1.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

fdm-app, dependencies, branch:development

Suggested reviewers

  • SvenVw
  • gerardhros

Poem

🐰 I hopped through code with nimble cheer,
Colors straightened, tooltips clear,
Bars now glow when I pass by,
Shadows settled, charts reach high,
A little hop — migration's here!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses most core requirements from issue #581 but lacks evidence of completing several critical acceptance criteria. Verify pnpm typecheck passes, complete visual smoke tests on all five chart pages, ensure no hsl(var(--chart- patterns remain, and confirm isFront removal from mineralization charts.
Out of Scope Changes check ⚠️ Warning Shadow styling change in card.tsx and export reordering in card.tsx and chart.tsx appear unrelated to the recharts migration scope. Remove the shadow-sm→shadow change and export reordering from card.tsx, or clarify how they support the recharts v2→v3 migration objective.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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 'Migrate recharts from v2 to v3' directly and concisely summarizes the primary change across all modified files.

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

✨ 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 FDM581

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.

@sentry
Copy link
Copy Markdown

sentry Bot commented Apr 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@BoraIneviNMI
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@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 (3)
fdm-app/app/components/blocks/balance/nitrogen-chart.tsx (2)

54-55: Type-laundering cast to satisfy Record<string, number | undefined>.

name: "Balans" as unknown as undefined intentionally inserts a string into a record typed as numeric, relying on the double cast to silence TypeScript. This leaks through to chartData[name] on line 574, where the tooltip renders "Balans" (string) followed by the unit — harmless today only because the "name" bar never receives hover focus, but it is a latent bug waiting for future tooltip changes.

Consider widening the type instead of laundering the value:

♻️ Suggested refactor
-    const chartData: Record<string, number | undefined> = {
-        name: "Balans" as unknown as undefined, // Needed for chart to render at y-axis points called "Balans" - see the JSX below
+    const chartData: Record<string, number | string | undefined> = {
+        name: "Balans", // YAxis category label; see JSX below

…and narrow back to number at the tooltip render site (e.g. typeof chartData[name] === "number" ? chartData[name].toLocaleString() : null).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/components/blocks/balance/nitrogen-chart.tsx` around lines 54 -
55, The chartData object is currently type‑laundered via name: "Balans" as
unknown as undefined which injects a string into a numeric record; change the
chartData type to allow string labels (e.g. Record<string, number | string |
undefined> or introduce a separate label key) and remove the cast on name so the
literal "Balans" is stored with the correct type, then at the tooltip render
site (where chartData[name] is read, referenced around the tooltip logic near
line 574) narrow the value with a runtime check like typeof chartData[name] ===
"number" ? formatNumber(...) : null (or skip rendering the numeric unit) so
strings never get formatted as numbers in the tooltip.

490-512: Consider pairing onMouseOver with onMouseOut per-bar for reliable outline clearing.

Outline focus is set on <Bar onMouseOver> and cleared only on the outer ChartContainer's onMouseLeave. When the cursor moves between bars quickly, this works (each bar sets a new focus), but moving off a bar onto the chart's padding — while still inside ChartContainer — leaves the previous bar outlined until the cursor fully exits. Adding onMouseOut={() => setBarOutlineFocus("")} on each <Bar> (or using onMouseLeave) eliminates the stale outline.

Also: the clear-state is set to "" (empty string) on line 518 while the state type is string | number | undefined — prefer undefined for consistency with the initial value so barOutlineFocus && ... guards read uniformly.

♻️ Suggested refactor
-                            onMouseOver={() => setBarOutlineFocus(dataKey)}
+                            onMouseOver={() => setBarOutlineFocus(dataKey)}
+                            onMouseOut={() => setBarOutlineFocus(undefined)}
@@
-            onMouseLeave={() => {
-                setBarOutlineFocus("")
-            }}
+            onMouseLeave={() => setBarOutlineFocus(undefined)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/components/blocks/balance/nitrogen-chart.tsx` around lines 490 -
512, The bar outline focus is only set via onMouseOver on each Bar (rendered in
the map) and cleared on the outer ChartContainer, which leaves a stale outline
when moving into chart padding; update each Bar to also call onMouseOut (or
onMouseLeave) with setBarOutlineFocus(undefined) to clear focus immediately, and
change the ChartContainer clear call (currently setting "") to
setBarOutlineFocus(undefined) so the state type (string | number | undefined) is
consistent and the barOutlineFocus checks behave uniformly; target the Bar
elements and the setBarOutlineFocus usage to implement these changes.
fdm-app/app/components/ui/chart.tsx (1)

176-189: nestLabel is computed against the unfiltered payload.

const nestLabel = payload.length === 1 && indicator !== "dot" is evaluated before the filter((item) => item.type !== "none") on line 189. If the original payload has more than one entry but only one remains after filtering (a common case since Recharts v3 now emits extra type: "none" entries for hidden series/cursors), the tooltip will render in "flat" mode instead of the nested single-item mode, and vice-versa.

Consider filtering once and deriving nestLabel from the filtered result:

♻️ Suggested refactor
-        if (!active || !payload?.length) {
+        const visiblePayload = payload?.filter((item) => item.type !== "none")
+        if (!active || !visiblePayload?.length) {
             return null
         }

-        const nestLabel = payload.length === 1 && indicator !== "dot"
+        const nestLabel = visiblePayload.length === 1 && indicator !== "dot"
@@
-                    {payload
-                        .filter((item) => item.type !== "none")
-                        .map((item, index) => {
+                    {visiblePayload.map((item, index) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/components/ui/chart.tsx` around lines 176 - 189, The computed
nestLabel uses the raw payload before filtering which causes incorrect layout
when Recharts emits type: "none" entries; change the component to first compute
a filteredPayload = payload.filter(item => item.type !== "none') (or similar)
and then set const nestLabel = filteredPayload.length === 1 && indicator !==
"dot', and use filteredPayload in the subsequent map/filter logic and rendering
instead of the original payload so the single-item nested tooltip mode reflects
the post-filtered data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fdm-app/app/components/blocks/balance/nitrogen-chart.tsx`:
- Around line 540-545: The custom tooltip content passed to the recharts
component currently assumes payload is always defined and uses payload.length
and payload[0], which can throw when payload is undefined; update the content
render prop (the content={({ payload }) => { ... }} block) to first guard for
payload being undefined or null and return null, then check payload.length ===
0, and when reading payload[0] ensure it exists before accessing its name; also
account for upstream filtering (e.g., ChartTooltipContent may remove entries
with item.type === "none") so rely on safe existence checks and not on
payload[0] always corresponding to the expected item before looking up
chartConfig[name].

In `@fdm-app/app/components/ui/chart.tsx`:
- Around line 271-275: The tooltip currently hides legitimate zero values
because it uses a truthy check on item.value; update the conditional rendering
around {item.value && (...)} in the chart component so it only excludes
null/undefined (e.g., use item.value !== undefined && item.value !== null or a
helper isNil check) ensuring 0 (and other valid falsy numbers) still render
inside the <span> showing item.value.toLocaleString().
- Around line 224-250: The Tailwind classes in the div's className (e.g.,
"border-[--color-border] bg-[--color-bg]") are invalid for Tailwind v4 and
prevent CSS variables from applying; update those classes to use a valid v4
arbitrary CSS-variable form—either replace with the var() bracket form
(border-[var(--color-border)] bg-[var(--color-bg)]) or the v4 parentheses
shorthand (border-(--color-border) bg-(--color-bg)) so the inline style's
--color-bg / --color-border set by indicatorColor take effect; keep the rest of
the conditional class logic (indicator/dashed/line/dot, nestLabel) unchanged.

---

Nitpick comments:
In `@fdm-app/app/components/blocks/balance/nitrogen-chart.tsx`:
- Around line 54-55: The chartData object is currently type‑laundered via name:
"Balans" as unknown as undefined which injects a string into a numeric record;
change the chartData type to allow string labels (e.g. Record<string, number |
string | undefined> or introduce a separate label key) and remove the cast on
name so the literal "Balans" is stored with the correct type, then at the
tooltip render site (where chartData[name] is read, referenced around the
tooltip logic near line 574) narrow the value with a runtime check like typeof
chartData[name] === "number" ? formatNumber(...) : null (or skip rendering the
numeric unit) so strings never get formatted as numbers in the tooltip.
- Around line 490-512: The bar outline focus is only set via onMouseOver on each
Bar (rendered in the map) and cleared on the outer ChartContainer, which leaves
a stale outline when moving into chart padding; update each Bar to also call
onMouseOut (or onMouseLeave) with setBarOutlineFocus(undefined) to clear focus
immediately, and change the ChartContainer clear call (currently setting "") to
setBarOutlineFocus(undefined) so the state type (string | number | undefined) is
consistent and the barOutlineFocus checks behave uniformly; target the Bar
elements and the setBarOutlineFocus usage to implement these changes.

In `@fdm-app/app/components/ui/chart.tsx`:
- Around line 176-189: The computed nestLabel uses the raw payload before
filtering which causes incorrect layout when Recharts emits type: "none"
entries; change the component to first compute a filteredPayload =
payload.filter(item => item.type !== "none') (or similar) and then set const
nestLabel = filteredPayload.length === 1 && indicator !== "dot', and use
filteredPayload in the subsequent map/filter logic and rendering instead of the
original payload so the single-item nested tooltip mode reflects the
post-filtered data.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 467ba567-f444-4045-8e86-c2707803588f

📥 Commits

Reviewing files that changed from the base of the PR and between a13edc9 and 1a06acf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • fdm-app/app/components/blocks/balance/nitrogen-chart.tsx
  • fdm-app/app/components/blocks/balance/organic-matter-chart.tsx
  • fdm-app/app/components/ui/card.tsx
  • fdm-app/app/components/ui/chart.tsx
  • fdm-app/package.json

Comment thread fdm-app/app/components/blocks/balance/nitrogen-chart.tsx
Comment thread fdm-app/app/components/ui/chart.tsx
Comment thread fdm-app/app/components/ui/chart.tsx
@BoraIneviNMI BoraIneviNMI marked this pull request as ready for review April 22, 2026 13:45
Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw left a comment

Choose a reason for hiding this comment

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

Thanks!

Copy link
Copy Markdown
Contributor

@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)
fdm-app/app/components/blocks/balance/nitrogen-chart.tsx (1)

550-556: ⚠️ Potential issue | 🔴 Critical

Guard against undefined payload before calling array methods.

In Recharts v3, the payload parameter can be undefined (not just an empty array) during initial render and while the chart is inactive. Calling payload.find(...) on undefined will throw TypeError: Cannot read properties of undefined (reading 'find').

🐛 Proposed fix
                     shared={false}
                     content={({ payload }) => {
+                        if (!payload) return null
                         const validPayloadItem = payload.find(
                             (item) => item && item.type !== "none",
                         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/components/blocks/balance/nitrogen-chart.tsx` around lines 550 -
556, The tooltip renderer in nitrogen-chart.tsx assumes payload is always an
array; guard against payload being undefined before calling array methods: in
the content={({ payload }) => { ... }} handler, check that payload is an array
(e.g., if (!payload || !Array.isArray(payload)) return null) before using
payload.find and accessing its items (references: the content prop callback, the
payload variable, and validPayloadItem/name extraction). Ensure the early return
prevents any further payload processing when payload is undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@fdm-app/app/components/blocks/balance/nitrogen-chart.tsx`:
- Around line 550-556: The tooltip renderer in nitrogen-chart.tsx assumes
payload is always an array; guard against payload being undefined before calling
array methods: in the content={({ payload }) => { ... }} handler, check that
payload is an array (e.g., if (!payload || !Array.isArray(payload)) return null)
before using payload.find and accessing its items (references: the content prop
callback, the payload variable, and validPayloadItem/name extraction). Ensure
the early return prevents any further payload processing when payload is
undefined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3f5818dc-d863-40da-8db1-3ff5ff5af8c6

📥 Commits

Reviewing files that changed from the base of the PR and between 1a06acf and c1100b7.

📒 Files selected for processing (2)
  • fdm-app/app/components/blocks/balance/nitrogen-chart.tsx
  • fdm-app/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • fdm-app/package.json

@SvenVw SvenVw merged commit 0429bf0 into development Apr 28, 2026
13 checks passed
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.

Migrate recharts from v2 to v3

2 participants