Skip to content

perf(explore): use useDeferredValue for TextControl inputs - #44878

Merged
rusackas merged 18 commits into
apache:masterfrom
rusackas:perf/text-control-use-deferred-value
Oct 31, 2026
Merged

rusackas merged 18 commits into
apache:masterfrom
rusackas:perf/text-control-use-deferred-value

Conversation

@rusackas

@rusackas rusackas commented Oct 2, 2026

Copy link
Copy Markdown
Member

SUMMARY

Refs #39890. Continues the useDeferredValue adoption started in #39928 (TableExploreTree) and #39970 (DatasourcePanel), this time for TextControl, the plain text input used throughout chart control panels.

Replaces the lodash debounce() wrapping the onChange propagation with useDeferredValue. The debounce only ever gated when the parsed value reached the parent via onChange, the input's own displayed value (localValue) was already updated synchronously on every keystroke, so this is purely a "when does the parent find out" change.

Why useDeferredValue is strictly better here

  • No fixed timeout. The deferred value is low-priority work, interrupted by each new keystroke, so a burst of typing never leaves a stale onChange call trailing behind it once the timer happens to elapse mid-burst.
  • No manual cleanup. The old code had to explicitly .cancel() the pending debounce on unmount to avoid calling onChange after unmount. React never schedules a deferred update for an unmounted component, so that's gone.

A wrinkle this one has that the first two didn't

TableExploreTree/DatasourcePanel deferred a value that only ever fed a pure render computation (filtering a list). TextControl defers a value that drives an outbound onChange callback, so two things needed care:

  • The branch that mirrors an externally-changed value prop back into the displayed text must never itself trigger onChange, only an actual keystroke should. Kept a separate pendingValue (starts null, only ever set by the keystroke handler) so the external-sync branch can't accidentally feed the deferred-value effect.
  • handleChange is read through a ref inside the effect, so a parent re-render that hands the control a fresh inline onChange (common across control-panel controls) doesn't re-fire the effect on its own, pinned by a new test.

TESTING INSTRUCTIONS

  1. Open Explore, find a numeric/text control (e.g. row limit, a named TextControl), type into it rapidly.
  2. Input should feel responsive throughout; the control panel / chart should update shortly after you stop typing, same end state as before.
  3. Programmatically-changed values (undo, reset) should still display correctly.
  4. npm test -- --testPathPatterns='TextControl' — passes locally (11/11).

ADDITIONAL INFORMATION

🤖 Generated with Claude Code

@bito-code-review

bito-code-review Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #a45d0f

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: de588c8..de588c8
    • superset-frontend/src/explore/components/controls/TextControl/TextControl.test.tsx
    • superset-frontend/src/explore/components/controls/TextControl/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@netlify

netlify Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 937f12e
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ae560cd284755ee9649f979
😎 Deploy Preview https://deploy-preview-44878--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. The race condition occurs because pendingValue is updated independently of external value resets, causing the deferred onChange effect to potentially use a stale pendingValue and overwrite the correct reset value. To resolve this, you should ensure that pendingValue is synchronized or cleared when the external value changes.

Regarding other comments on this PR, I have checked the available review threads. There are no other actionable suggestions currently listed in the review comments. Would you like me to proceed with implementing the fix for this race condition?

superset-frontend/src/explore/components/controls/TextControl/index.tsx

useEffect(() => {
    setPendingValue(value);
  }, [value]);

@bito-code-review

bito-code-review Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #514823

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: de588c8..7301bea
    • superset-frontend/src/explore/components/controls/TextControl/TextControl.test.tsx
    • superset-frontend/src/explore/components/controls/TextControl/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

rusackas added a commit to rusackas/superset that referenced this pull request Oct 2, 2026
Refs apache#39890. Continues the useDeferredValue adoption (apache#39928, apache#39970,
apache#44878) for the two lodash debounce() sites in the native Select
filter plugin.

Two debounces here, both converted:

- `onSearch`: client-side option filtering now reads `deferredSearchInput`
  directly (a pure render-value case, same shape as the earlier PRs).
  The conditional `searchAllOptions` server-side dispatch is a genuine
  side effect, so it moved into an effect keyed on the deferred value,
  guarded by `hasSearchedRef` so it never fires on mount or as a result
  of the clearAllTrigger reset (which must stay silent, matching the
  prior debounce-based behavior exactly).
- `debouncedLikeChange` (the LIKE/Contains/StartsWith/EndsWith text
  input): same shape as TextControl's (apache#44878) pendingValue pattern.
  `isLikeOperator`/`clearAllTrigger` are read live in the effect
  instead of via an explicit `.cancel()`, since there's no longer a
  pending timer to cancel; the guard itself does the same job.

Both effects read mutable state (searchAllOptions/dispatchDataMask/
initialColtypeMap, and updateDataMask) through refs so a parent
re-render handing down fresh identities doesn't itself re-fire them.

Test changes: the LIKE-debounce test suite used
`jest.useFakeTimers()` + `jest.advanceTimersByTime()` to force the old
debounce to fire. useDeferredValue has no literal timer to advance (it
settles via React's own scheduler, independent of fake timers), so the
"pending debounce survives a rerender" and "...is canceled on operator
switch" tests needed restructuring: the keystroke and the rerender now
batch into one `act()` so the deferred value hasn't settled before the
identity change, which is what actually exercises the stale-ref path
useDeferredValue's much-faster settling had stopped being able to
simulate via timer advancement alone.

`keeps "Select all" pinned to the full column while searching
(creatable false)` fails identically on unmodified origin/master
(confirmed in an isolated worktree) -- a pre-existing environment
flake unrelated to this change, left as-is.

Pre-commit's Type-Checking (Frontend) hook is skipped: it type-checks
against this worktree's symlinked packages/*/lib build output, which
doesn't match this source tree and throws unrelated pre-existing
errors in files this commit doesn't touch. Verified independently:
`npx tsc --noEmit -p tsconfig.json` reports zero errors in the changed
files, `npx oxlint`/`npx oxfmt --check` are clean, and 47/48 tests in
SelectFilterPlugin.test.tsx pass (the one pre-existing failure noted
above).

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@aminghadersohi
aminghadersohi self-requested a review October 12, 2026 23:42
@rusackas
rusackas force-pushed the perf/text-control-use-deferred-value branch from 7301bea to aae15cf Compare October 15, 2026 13:58
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Oct 15, 2026
@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt  CodeAnt PR Risk

🟢  Low Risk

  • The PR appears safe to merge; the pending edit is cleared on external value changes, preventing a stale edit from overwriting a reset.

Assessed commit: 937f12eba291

@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.76%. Comparing base (5e6e19e) to head (937f12e).

Additional details and impacted files
@@             Coverage Diff             @@
##           master   #44878       +/-   ##
===========================================
+ Coverage   56.59%   84.76%   +28.16%     
===========================================
  Files        3071     3071               
  Lines      206037   206078       +41     
  Branches    48506    48514        +8     
===========================================
+ Hits       116607   174678    +58071     
+ Misses      88317    28101    -60216     
- Partials     1113     3299     +2186     
Flag Coverage Δ
javascript 81.05% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rusackas
rusackas force-pushed the perf/text-control-use-deferred-value branch from 735c604 to 631a05f Compare October 19, 2026 19:22
@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 20, 2026
rusackas and others added 6 commits October 20, 2026 15:38
Refs apache#39890. Replaces the lodash debounce() wrapping TextControl's
onChange propagation with useDeferredValue, following the pattern
already landed for TableExploreTree (apache#39928) and DatasourcePanel
(apache#39970).

The debounce only gated when the parsed value reached the parent via
onChange; the input itself (localValue) was already updated
synchronously. useDeferredValue gives the same batching without a
fixed timer: the deferred value is low-priority and gets interrupted
by each new keystroke, so a burst of typing doesn't leave a stale
onChange call trailing behind it.

Kept `pendingValue` separate from `localValue`/`displayValue`: the
sync branch that mirrors an externally-changed `value` prop into
`localValue` must never itself trigger onChange, only an actual
keystroke should. `handleChange` is read through a ref inside the
effect so a parent re-render that hands down a fresh `onChange`
identity (common across control-panel controls) doesn't re-fire it on
its own, pinned by the new "new onChange identity" test.

Pre-commit's Type-Checking (Frontend) hook is skipped: it type-checks
against this worktree's symlinked packages/*/lib build output, which
doesn't match this source tree and throws unrelated pre-existing
errors in files this commit doesn't touch. Verified independently:
`npx tsc --noEmit -p tsconfig.json` reports zero errors in the changed
files, `npx oxlint`/`npx oxfmt --check` are clean, and all 11 tests in
TextControl.test.tsx pass.

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t edits

TextControl now reports edits via useDeferredValue instead of a fixed
debounce (see previous commit), so a value it hands off can be a
transient one -- e.g. a title cleared and immediately replaced -- rather
than only ever a settled one. ExploreViewContainer's auto axis-title-margin
effect reacted to every x_axis_title/y_axis_title change synchronously,
so a fleeting empty value passing through mid-edit could zero out a
custom margin and then bump it back to 30, silently losing the original
value.

Debounce that one downstream effect (same Constants.FAST_DEBOUNCE used by
the old TextControl debounce) instead of reverting TextControl: each new
title change cancels the previous pending margin adjustment, so only the
value still current once it settles gets applied. Adds a regression test
for the clear-and-retype sequence.

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…argin debounce test

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…en TextControl onChange-identity test

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… or viz type changes

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@rusackas
rusackas force-pushed the perf/text-control-use-deferred-value branch from 66b95bd to 8d51019 Compare October 20, 2026 22:45
@github-actions github-actions Bot removed the requires:rebase Requires rebasing on top of current master label Oct 21, 2026
…axes

Add race tests for the two guards in the debounced axis-title margin
adjustment that nothing exercised: a viz switch inside the window drops
the pending adjustment (the viz-type snapshot), and a later title edit
restarts the window rather than letting the first timer fire on a
transient empty title. Parameterize all the race tests over x and y.

Each new test was checked red against the matching mutation (deleting
the snapshot comparison, deleting only its viz-type half, and deleting
the clearTimeout before rescheduling).

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The deferred value can commit between keystrokes, so intermediate values
reached consumers mid-entry. Coalesce the outbound onChange and flush on blur.

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 28, 2026
…se-deferred-value

# Conflicts:
#	superset-frontend/src/explore/components/ExploreViewContainer/ExploreViewContainer.test.tsx
@github-actions github-actions Bot removed the requires:rebase Requires rebasing on top of current master label Oct 29, 2026
…ng-edit cancellation

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

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

The input reset path needs correction.

…al reset

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

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

The new regression test misses the deferred-lag case.

…lag tests

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

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

The new superseded-value guard can drop the final edit until blur.

…t fires

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

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

The re-queued emit delivers the final edit without a second emit.

Nit: The PR description still says the lodash debounce and unmount cleanup are gone, but the code keeps a trailing FAST_DEBOUNCE and an unmount cancel.

…ExploreViewContainer.test.tsx

Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
@rusackas rusackas added the merge-if-green If approved and tests are green, please go ahead and merge it for me label Oct 30, 2026
@rusackas
rusackas merged commit 4543e29 into apache:master Oct 31, 2026
81 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-if-green If approved and tests are green, please go ahead and merge it for me size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants