Skip to content

refactor: use x, defaultX and onXChange for state props - #983

Open
rohanchkrabrty wants to merge 4 commits into
feat/change-event-detailsfrom
refactor/controlled-state-props
Open

rohanchkrabrty wants to merge 4 commits into
feat/change-event-detailsfrom
refactor/controlled-state-props

Conversation

@rohanchkrabrty

@rohanchkrabrty rohanchkrabrty commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Breaking: state props use x, defaultX and onXChange. Select searchValue, defaultSearchValue, onSearch become inputValue, defaultInputValue, onInputValueChange. DatePicker and RangePicker onSelect becomes onValueChange. Tour onStepChange becomes onStepIndexChange, with the step in details.step. DataView onTableQueryChange becomes onQueryChange.
  • Breaking: DataView query and FilterChip value were read on the first render only. They are now controlled with Base UI useControlled, and defaultQuery and defaultValue set the first value. DatePicker value also moves to useControlled, and value={null} is a controlled empty date.
  • FilterChip adds operation and defaultOperation. DataView passes each filter's operator to its chip, so a query with operator: 'neq' shows "is not".
  • Every new callback follows feat: pass a details object to change callbacks #979: Select forwards Base UI eventDetails. DatePicker, RangePicker, Tour, DataView and FilterChip onOperationChange pass details from createChangeDetails.
  • Docs: props tables, demos, mdx pages, the upgrading page, the consumer skill and the FilterChip Code Connect template. Tests cover the renames, the controlled DataView query, FilterChip value and operation, and a controlled DatePicker.

Stacked on #979. Retarget to main after it merges.

Decisions

  • Each callback this PR adds or renames runs before the state update, as in feat: pass a details object to change callbacks #979. Select skips the update when eventDetails.isCanceled.
  • FilterChip reports an operation change through the existing onOperationChange, now (operation, details). onValueChange keeps details.operation from feat: pass a details object to change callbacks #979. The details.reason comes from the operation Select.
  • DataView keeps its internal query, because the wire query leaves out filters with no value. A query from outside replaces it only when the two differ. A copy of the reported query, for example a JSON copy from the URL, counts as the same query, so a new empty filter chip stays. A query change from outside does not call onQueryChange.
  • DataView calls onQueryChange in client mode too, so a controlled query works in both modes. Server mode still calls it once on mount, as onTableQueryChange did, also under StrictMode. details.reason is none, because the call runs after the state change.
  • A controlled DataView query follows React's controlled rule: if the parent does not pass the new query back, the view returns to the query prop.
  • DatePicker still commits a typed date on Enter, blur or an outside click. It now calls onValueChange only when the typed date differs from the value. A day click passes reason item-press and a typed date passes input-change.
  • defaultOperation falls back to the first operation, as before. FilterChip keeps the typed text of a number column, so 1. and - stay editable under a controlled value.
  • docs/rfcs/003 and docs/V1-migration.md keep the old names, since they record earlier releases. DataTable keeps query and onTableQueryChange.

Builds on merged PRs

@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
apsara Ready Ready Preview Oct 22, 2026 5:08am UTC

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 467109c2-eb39-4817-8de3-f84cfa17662c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@pkg-pr-new

pkg-pr-new Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/@raystack/apsara@983

commit: de53b19

A JSON copy of the reported query no longer resets the filters. Server mode reports the mount query once under StrictMode. onQueryChange runs before the state update.
A controlled DatePicker can start empty with value={null}. onValueChange runs before the state update.

/** Compares two wire queries by value, so a JSON copy matches its source. */
export const isSameQuery = (a?: DataViewQuery, b?: DataViewQuery): boolean =>
JSON.stringify(a) === JSON.stringify(b);

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.

suggestion: JSON.stringify depends on key order, so a parent that rebuilds the query (say from URL params, { filters, sort } vs { sort, filters }) won't match.

That falls through to the reset path, which drops filter chips that have no value yet, the case this check exists to protect.

Could we compare in an order-independent way, for example by sorting keys before stringifying?

key={filter.name}
label={filter.label}
value={filter.value ?? undefined}
value={filter.value ?? ''}

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.

suggestion: The ?? '' here keeps the chip controlled from its first render, but DataTable's copy (data-table/components/filters.tsx:151) still passes value={filter.value}.

I rendered a FilterChip with value={undefined} and then value="abc": Base UI logs "changing the uncontrolled value state of FilterChip to be controlled", and the input stays empty.

Could we add the same guard on the DataTable side?

fields: DataViewField<TData>[];
/** Initial query. Transformed to the internal shape on mount. */
/** The query (controlled). */
query?: DataViewQuery;

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.

question: DataTable keeps a read-once query (its JSDoc says "Initial query") and onTableQueryChange, so after this PR the same prop name means "controlled" on DataView and "first value" on DataTable. Is DataTable getting the same change in a follow-up, or should its docs call out the difference?

(nextIndex: number) => {
onStepIndexChangeRef.current?.(
nextIndex,
createChangeDetails('none', undefined, {

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.

nit: Every step change reports reason none with a placeholder event, including Next and Back clicks.
Sidebar and ChatPanel pass trigger-press with the click event (from #979), so could the buttons pass theirs through setIndex, keeping none for imperative calls?

This branch was successfully deployed

1 active deployment
Preview — de53b19a Deployed Oct 22, 2026 by vercel[bot]
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.

2 participants