Skip to content

[BREAKINGCHANGE] Upgrade Oxlint and fix React Compiler errors - #310

Merged
Gladorme merged 4 commits into
perses:mainfrom
Gladorme:t3code/upgrade-ox-tools-fix-compiler-memo
Oct 1, 2026
Merged

Gladorme merged 4 commits into
perses:mainfrom
Gladorme:t3code/upgrade-ox-tools-fix-compiler-memo

Conversation

@Gladorme

@Gladorme Gladorme commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

Description

Enforcing stricter rules around React to be compliant with React Compiler rules (https://oxc.rs/blog/2026-08-18-react-compiler-support).

Breaking changes:

  • memo.ts => Removed, replace useDeepMemo and useMemoized by React useMemo.
  • client/src/model/resource.ts => Resource spec changes from any to unknown.
  • components/src/Table/model/table-model.ts => TableColumnConfig cell values are now unknown, and cellDescription now takes CellContext<T, unknown>.

Screenshots

Checklist

  • Pull request has a descriptive title and context useful to a reviewer.
  • Pull request title follows the [<catalog_entry>] <commit message> naming convention using one of the
    following catalog_entry values: FEATURE, ENHANCEMENT, BUGFIX, BREAKINGCHANGE, DOC,IGNORE.
  • All commits have DCO signoffs.

UI Changes

  • Changes that impact the UI include screenshots and/or screencasts of the relevant changes.
  • Code follows the UI guidelines.
  • E2E tests are stable and unlikely to be flaky.
    See e2e docs for more details. Common issues include:
    • Is the data inconsistent? You need to mock API requests.
    • Does the time change? You need to use consistent time values or mock time utilities.
    • Does it have loading states? You need to wait for loading to complete.

@Gladorme Gladorme changed the title [IGNORE] Upgrade Oxlint and fix React Compiler errors - #826 [IGNORE] Upgrade Oxlint and fix React Compiler errors Sep 19, 2026
@Gladorme
Gladorme force-pushed the t3code/upgrade-ox-tools-fix-compiler-memo branch 3 times, most recently from c001021 to 21daa42 Compare September 23, 2026 09:50
@Gladorme
Gladorme marked this pull request as ready for review September 23, 2026 09:50
@Gladorme
Gladorme requested a review from a team as a code owner September 23, 2026 09:50
@Gladorme Gladorme changed the title [IGNORE] Upgrade Oxlint and fix React Compiler errors [BREAKINGCHNANGE] Upgrade Oxlint and fix React Compiler errors Sep 23, 2026
@Gladorme Gladorme changed the title [BREAKINGCHNANGE] Upgrade Oxlint and fix React Compiler errors [BREAKINGCHANGE] Upgrade Oxlint and fix React Compiler errors Sep 23, 2026
@Nexucis

Nexucis commented Sep 23, 2026

Copy link
Copy Markdown
Member

on the paper I am fine with the changes. Just wondering how much the breaking changes are ok.
@jgbernalp what is your opinion on that ?

const tooltipElementRef = useRef<HTMLDivElement | null>(null);

const mousePos = useMousePosition();
const { mousePos, nearbySeries } = useChartTooltipData({

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.

This seems a change that can be it's own PR

@jgbernalp

Copy link
Copy Markdown
Contributor

Not sure why a change in the linter will create breaking changes on the code. Is this because of react compiler? If so I'll suggest to split this PR into several ones, oxlint changes, react compiler, etc.. I also saw some new performance tests and new components being created, I think the changes are necessary but at the current stage they are hard to review.

@Gladorme

Gladorme commented Sep 23, 2026 •

Copy link
Copy Markdown
Member Author

Yeah I added a optimization on timeserieschart tooltip on the way, because I found timeserieschart slower after lint fixes 😅
Still breaking change, because removing custom memo hooks, not compatible with react compiler.

I did something similar on plugin PR with improvement for timeserieschart.

But yeah I will split PR 🥲

- Upgrade oxlint to 1.85.0 and oxfmt to 0.70.0
- Enable React Compiler and React Doctor rules as errors
- Fix violations and remove useMemoized/useDeepMemo

Signed-off-by: Guillaume LADORME <gladorme@gmail.com>
Enable typescript/no-explicit-any as an error and replace existing violations with checked types. Narrow resource display settings, type table cell values, and pass only available query data to panel actions.

Signed-off-by: Guillaume LADORME <gladorme@gmail.com>
@Nexucis

Nexucis commented Sep 24, 2026

Copy link
Copy Markdown
Member

I guess this is not a breaking change now. Can you update the title @Gladorme ?

@Gladorme

Copy link
Copy Markdown
Member Author

I guess this is not a breaking change now. Can you update the title @Gladorme ?

memo.ts is still removed

@Nexucis

Nexucis commented Sep 25, 2026

Copy link
Copy Markdown
Member

since the PR has been splitted, what do you think now @jgbernalp ?

@Nexucis

Nexucis commented Sep 25, 2026

Copy link
Copy Markdown
Member

Review from Opus 5.5:

Review: #310, "[BREAKINGCHANGE] Upgrade Oxlint and fix React Compiler errors"

Verdict: Request changes. One bug causes an infinite render loop, and a few breaking changes are missing from the PR description. The rest of the PR is good cleanup.

Checks run on the PR branch: lint (0 errors), format:check, type-check, and test all pass (1,356 tests). I reviewed with the vercel-react-best-practices skill.

🔴 Must fix

1. Infinite update loop in plugin-editor-api.ts

  • Before, the pending selection was cleared right after onChange fired. Now it's cleared only when value.selection matches the requested selection exactly, including metadata.version and metadata.registry.
  • DatasourceEditorForm, AnnotationEditorForm and QueryEditorContainer all drop metadata in their onChange, and they pass a new inline value object on every render.
  • So when a user picks a versioned or registry variant in PluginKindSelect, the selection never matches. The effect calls onChange(createInitialOptions()) again on every render.
  • I reproduced it with a throwaway test: the PR hits "Maximum update depth exceeded", and the same test passes on main.
  • Suggested fix: clear the pending state once the load has been applied, for example with a ref or state keyed on the request, instead of comparing against the parent's value.

🟠 Should fix

2. Undocumented breaking type changes. The PR description lists only the memo.ts removal. It should also list:

  • Resource.spec changes from any to unknown.
  • TableColumnConfig cell values are now unknown, and cellDescription now takes CellContext<T, unknown>. This breaks existing code such as perses/perses DashboardTreeList.tsx (const tags: string[] | undefined = getValue(), formatAbsoluteTime(getValue())).

3. Removing useDeepMemo affects perses/plugins. gaugechart/src/GaugeChartBase.tsx uses it. Swapping it for useMemo would bring back the recompute-on-hover that its comment warns about. That PR needs to land alongside this one.

4. TextField can drop typed input.

  • useEffect(() => () => debounceFn.cancel(), [debounceFn]) also cancels whenever onChange changes identity, not just on unmount.
  • Callers that pass an inline onChange (e.g. TransformEditor) lose the pending value if the parent re-renders within the debounce window.
  • Suggested fix: keep the latest onChange in a ref and cancel only on unmount. Consider flushing on unmount instead of cancelling.

🟡 Worth discussing

  1. HTTPSettingsEditor now calls onChange on mount when neither directUrl nor proxy is set, even in read-only mode. That can make DatasourceEditorForm show the discard dialog when the user changed nothing.
  2. TimeChartTooltip now computes nearbySeries in a layout effect and stores it in state. That means two renders per mouse move on a hot path. Moving it out of render is justified because the calculation calls dispatchAction (a side effect), but the pure calculation could stay in render with only the highlight dispatch in an effect.
  3. useId now returns React's :r0:-style IDs. These contain colons, so any consumer that builds #id CSS selectors must escape them.
  4. EChart now requires ResizeObserver. Downstream jsdom tests that don't mock EChart will throw.

⚪ Nits

  • UsageMetricsProvider: ctx is still rebuilt on every render (new Map, counters reset); that bug predates this PR. Moving the mutation into recordQuery only hides it from the linter. Holding ctx in useState would actually fix it.
  • use-save-dashboard / DatasourceTestConnectionButton: a one-line comment explaining the Promise.then().finally() style (I assume the compiler doesn't support try/finally) would help readers.
  • VirtualizedTable: the columnSizing and columnSizingInfo props are now unused.

👍 Good changes

  • PluginLoaderComponent now remounts on the full plugin identity and ignores stale loads.
  • DashboardProvider no longer creates a new store on every render.
  • EChart uses a ResizeObserver instead of the fragile sx/style effect.
  • TextVariable derives its state during render instead of in an effect.
  • Mutations of props were removed and new tests were added.

Apply plugin initialization once per selection request even when the parent omits metadata, and ignore stale loads. Keep pending text input across onChange callback changes. Add regression coverage for both review findings and selection races.

Signed-off-by: Guillaume <gladorme@gmail.com>
@Gladorme
Gladorme force-pushed the t3code/upgrade-ox-tools-fix-compiler-memo branch from b72b6bf to 652c336 Compare September 25, 2026 10:42
Comment thread dashboards/src/components/Panel/Panel.tsx Outdated
Comment thread components/src/ThresholdsEditor/ThresholdsEditor.tsx
Signed-off-by: Guillaume LADORME <Gladorme@users.noreply.github.com>
@Nexucis

Nexucis commented Sep 25, 2026

Copy link
Copy Markdown
Member

🤖 This review was done by GitHub Copilot (AI coding agent, powered by Claude Opus 5.5), on behalf of @Nexucis. It's a comment only, not an approval.

Re-review of head 4088252

Checks run locally: lint (0 errors, no warnings added), format:check, type-check and test all pass.

Earlier points

# Issue Status
1 Infinite update loop in usePluginEditor when the owner drops metadata (DatasourceEditorForm, AnnotationEditorForm and QueryEditorContainer all do) ✅ Fixed. I reran my repro (a parent that drops metadata, then selecting a versioned variant): it hit "Maximum update depth exceeded" on the previous head and now calls onChange exactly once. A failed load now reports the error without calling onChange. The new stale-load and unmount tests are good.
2 Breaking type changes (Resource.spec, TableColumnConfig) not documented ✅ Now listed in the PR description.
3 useDeepMemo is still used by perses/plugins (GaugeChartBase.tsx) ✅ Handled in perses/plugins#826. The two PRs should be merged together.
4 TextField dropped pending debounced input when onChange identity changed ✅ Fixed with the latest-ref pattern, and covered by a test.
— Plugin actions only received queries with data ✅ Reverted: all queries are passed again, in order.

Still open (non-blocking, could be follow-ups)

  1. HTTPSettingsEditor calls onChange on mount when neither directUrl nor proxy is set, even when isReadonly. Because DatasourceEditorForm compares the current form values with the initial definition on cancel, this can show the discard dialog when the user changed nothing.
  2. TimeChartTooltip renders twice per mouse move. nearbySeries is now computed in a layout effect and stored in state. Moving it out of render makes sense because getNearbySeriesData calls dispatchAction (highlight/downplay), which is a side effect. But this is a hot path, so it could keep the pure calculation in render and move only the dispatch calls into an effect.
  3. useId now returns React's :r0:-style IDs. These contain colons, so any consumer that builds #id selectors (for example, for document.querySelector) has to escape them. Worth a note in the breaking changes.
  4. EChart now needs ResizeObserver. jsdom doesn't provide it, so downstream tests that render EChart without mocking it will throw. Most perses/plugins tests mock EChart, so the risk is low.

New comments on the latest commits (non-blocking)

  • Panel.tsx: action.component as ComponentType<typeof panelPropsForActions> hides a mismatch in the public contract. PanelProps.queryResults[].data is typed as always present, but actions can receive undefined. Longer term, the action props type should declare data?: T so plugin authors handle it.
  • plugin-editor-api.ts:
    • The ['getPlugin', type, kind, version, registry] query key is now duplicated from usePlugin in plugin-registry.ts. A shared key helper would stop the two from drifting apart and silently losing cache sharing.
    • queryClient.fetchQuery defaults to retry: false, while the old usePlugin (useQuery) retried 3 times. Remote plugin loads will now fail on the first network error. Consider setting retry explicitly.
    • The comment on reset() depends on react-query v4 internals, and isLoading becomes isPending for mutations in v5. This will need another look during the v5 upgrade.
  • Nits:
    • UsageMetricsProvider still rebuilds ctx (a new Map, counters reset to 0) on every render. That predates this PR, and moving the mutation into recordQuery mostly hides it from the compiler lint. Keeping ctx in useState or a ref would actually fix it.
    • VirtualizedTable no longer uses the columnSizing and columnSizingInfo props.
    • A short comment on why use-save-dashboard and DatasourceTestConnectionButton use Promise.then().finally() instead of try/finally (I assume React Compiler doesn't support finally) would help future readers.

👍 Good changes

  • PluginLoaderComponent now remounts on the full plugin identity (module, name, registry, version, base URL) and ignores stale loads.
  • DashboardProvider no longer creates a store on every render.
  • EChart uses a ResizeObserver instead of the fragile sx/style effect.
  • TextVariable derives its state during render instead of in an effect.
  • Mutations of props are removed, and there's good new test coverage.

@Nexucis

Nexucis commented Sep 29, 2026

Copy link
Copy Markdown
Member

@Gladorme based on the AI said, it is worth to consider the point 3 for the documentation at least

@Gladorme

Copy link
Copy Markdown
Member Author

Will be removed in a next PR (#319, PR will be split in multiple PRs once this one is merged)

@Nexucis

Nexucis commented Sep 30, 2026

Copy link
Copy Markdown
Member

I am ok to move forward with this PR.

@jgbernalp do you have any other concerns ?

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

LGTM I ran the e2e tests in Perses with this shared PR and it worked "ok", there seems to be an issue with a deepMemo function, but seems unrelated to this changes.
Screenshot 2026-10-01 at 11 27 25

@Gladorme

Gladorme commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

useDeepMemo has been removed, I will update to standard useMemo in plugin when bumping version

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