Skip to content

perf(sql-lab): use useDeferredValue for schema browser search - #39928

Merged
rusackas merged 1 commit into
masterfrom
feat/use-deferred-value-tree-search
May 11, 2026
Merged

rusackas merged 1 commit into
masterfrom
feat/use-deferred-value-tree-search

Conversation

@rusackas

@rusackas rusackas commented May 7, 2026

Copy link
Copy Markdown
Member

SUMMARY

Refs #39890. First useDeferredValue adoption following the React 18 upgrade.

Replaces the fixed-delay useDebounceValue with useDeferredValue in TableExploreTree's schema browser search (the recently-debounced search from #39489).

Why useDeferredValue is strictly better here

  • No fixed timeout. The deferred update runs as low-priority work and is automatically interrupted on each new keystroke, so the filter never lingers behind the input.
  • Input stays responsive because it reads searchTerm directly (line 472 of the file). The expensive tree-filter computation reads deferredSearchTerm instead.
  • One fewer custom-hook dependency. Uses the React primitive.

The other useDebounceValue consumers in the codebase (SQL editor auto-save, SQL validation, JS editor parsing) debounce API calls or expensive parsing — not UI filtering — so they stay debounced. They're listed in #39890 for case-by-case follow-up but are not the same pattern.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

No visual change. Behaviorally: typing into the schema-browser search field should feel snappier — the input never lags while the tree filter catches up, because React deprioritizes the filter under typing pressure.

TESTING INSTRUCTIONS

  1. Open SQL Lab and pick a database with many schemas/tables.
  2. Type into the schema-browser search field rapidly. Input should feel responsive throughout.
  3. Filter results should converge after you stop typing — same end state as before.
  4. Clearing the field should restore the full tree.
  5. npm test -- --testPathPatterns='TableExploreTree' — passes locally (11/11).

ADDITIONAL INFORMATION

Replace the fixed-delay useDebounceValue with React 18's useDeferredValue
in TableExploreTree's tree filter. Refs #39890.

useDeferredValue is strictly better for this use case:
- No fixed timeout — the deferred update runs as low-priority work and
  is interrupted automatically on each new keystroke.
- The user-facing input stays responsive (it reads searchTerm directly),
  while the expensive tree filter computation reads deferredSearchTerm.
- Removes one custom hook dependency from this component.

Other useDebounceValue consumers (SQL editor auto-save, validation,
JS editor) are debounced API calls, not UI filters, and stay as-is.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented May 7, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #f56d62

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset-frontend/src/SqlLab/components/TableExploreTree/index.tsx - 1
    • Performance Regression from Removed Debouncing · Line 21-21
      Replacing useDebounceValue with useDeferredValue removes the 250ms debouncing delay, causing search filtering to occur on every keystroke instead of after the user stops typing. This can lead to performance issues with large tree data, as hasMatchingNodes and searchMatch computations run more frequently. The original debouncing was appropriate for search inputs to avoid excessive re-renders and computations.
Review Details
  • Files reviewed - 1 · Commit Range: 5f2e0fb..5f2e0fb
    • superset-frontend/src/SqlLab/components/TableExploreTree/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 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

@dosubot dosubot Bot added change:frontend Requires changing the frontend sqllab Namespace | Anything related to the SQL Lab labels May 7, 2026
@rusackas
rusackas requested a review from msyavuz May 8, 2026 16:12
@rusackas
rusackas merged commit eed7098 into master May 11, 2026
70 of 73 checks passed
@rusackas
rusackas deleted the feat/use-deferred-value-tree-search branch May 11, 2026 16:47
qfcwell pushed a commit to qfcwell/superset that referenced this pull request May 12, 2026
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>
rusackas added a commit to rusackas/superset that referenced this pull request Oct 15, 2026
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>
rusackas added a commit to rusackas/superset that referenced this pull request Oct 19, 2026
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>
rusackas added a commit to rusackas/superset that referenced this pull request Oct 20, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:frontend Requires changing the frontend size/S sqllab Namespace | Anything related to the SQL Lab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants