Repository navigation
[DataGrid] Expose apiRef on GridCallbackDetails for selector access in callbacks - #22973
Conversation
…allback Signed-off-by: michel <jsnerdic@gmail.com>
Signed-off-by: michel <jsnerdic@gmail.com>
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
Signed-off-by: michel <jsnerdic@gmail.com>
|
This method should work as a workaround, although I’m not sure it’s something we should document. An alternative could be to expose Also, I think this would fit better under the https://mui.com/x/react-data-grid/state/#access-the-state section rather than the selection docs, since it applies to all event handlers that receive callback arguments, not just selection related ones. |
Signed-off-by: michel <jsnerdic@gmail.com>
Thanks for the review! I agree with exposing // GridCallbackDetails
apiRef: React.RefObject<GridApiCommon>;And in The demo would then simplify to: const updateSelectedDesks = (details: GridCallbackDetails) => {
const selectedRows = gridRowSelectionIdsSelector(details.apiRef);
...
};I'll update this PR to add |
apiRef on GridCallbackDetails for selector access in callbacks
apiRef on GridCallbackDetails for selector access in callbacksapiRef on GridCallbackDetails for selector access in callbacks
… access in event handlers Signed-off-by: michel <jsnerdic@gmail.com>
…API access Signed-off-by: michel <jsnerdic@gmail.com>
LukasTy
left a comment
There was a problem hiding this comment.
Verdict: right direction and the runtime plumbing is complete (every control-state callback goes through the single propOnChange call site you patched, so nothing is left unpopulated), but the type is too narrow for Pro/Premium, the docs section needs a controlled-mode caveat, and there are no tests.
-
apiRef: RefObject<GridApiCommon>means Pro/Premium selectors do not compile through it -- which is the exact use case being documented. Both of these failtsgowith TS2345 (GridStateCommunityis missingcolumnReorder, detailPanel, dataSource/rowGrouping, aggregation, cellSelection, pivoting, ...):<DataGridPro onDetailPanelExpandedRowIdsChange={(ids, details) => gridDetailPanelExpandedRowIdsSelector(details.apiRef)} /> <DataGridPremium onRowGroupingModelChange={(model, details) => gridRowGroupingSanitizedModelSelector(details.apiRef)} />GridCallbackDetailsalready takes one generic (K); addingApi extends GridApiCommon = GridApiCommonand threadingGridApiPro/GridApiPremiumthroughdataGridProProps.ts/dataGridPremiumProps.tsfixes it. The demo only compiles because it happens to use a community selector. -
In controlled mode the selector returns stale state, and the new docs section does not mention it.
useGridStateInitialization.setStatesetsignoreSetStateand skips the state commit when the prop wins, then callspropOnChange. Verified: withsortModelcontrolled,onSortModelChangereceivesmodel = [{ field: 'brand', sort: 'asc' }]whilegridSortModelSelector(details.apiRef)returns[]; same divergence forrowSelectionModel. Uncontrolled is correct. The page already carries a warning about controlled models under "Save and restore the state" -- this section needs a matching:::warning. -
The demo uses
DataGridProfor a community-only feature. Every other demo indocs/data/data-grid/state/usesDataGridfrom@mui/x-data-grid, and bothgridRowSelectionIdsSelectoranddetails.apiRefare community. Nothing in it needs Pro. -
No tests.
apiRefis now a required public field populated from three separate paths (publishEvent,propOnChange, the two editing hooks) and none of them is covered. -
useGridStateInitialization.ts:94inlines{ current: apiRef.current.getPublicApi() }instead of thegetPublicApiRef(apiRef)helper this same PR uses in both editing hooks. Related:publishEventpasses the grid's own internalpublicApiRef, so user code assigningdetails.apiRef.currentwould corrupt the grid -- usinggetPublicApiRefin all three places is both consistent and safe. Also, the object spread inpublishEventdoubles the per-event allocation on the grid's hottest path; a plain conditional assignment avoids it.
…des consistent and type-safe API access in event handlers Signed-off-by: michel <jsnerdic@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dw4bjYohMj5g5QuALoFXoW
- `GridControlStateItem` and `GridStatePrivateApi` take an `Api` generic (defaulted to `GridApiCommon`), instantiated with the package-specific public API through `GridPrivateOnlyApiCommon`, so Pro/Premium `registerControlState` accepts the narrowed `propOnChange` handlers natively and the `as` casts are gone - add exported `GridCallbackDetailsPro`/`GridCallbackDetailsPremium` aliases and use them in the prop declarations - make `getPublicApiRef` return a per-instance cached, getter-based ref: no allocation on the `publishEvent` hot path and the ref cannot be used to re-point the grid's internal ref - pin the stable-ref behavior and the controlled-model staleness caveat with tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dw4bjYohMj5g5QuALoFXoW
|
@LukasTy I hardened the types, threaded the apiRef correctly through the state mechanism and added some tests for each of the packages (community, pro and premium). Could you please re-review it in this state? Thanks! |
| newRowSelectionModel: GridRowSelectionModel, | ||
| details: GridCallbackDetails, | ||
| ) => { | ||
| updateSelectedDesks(details); |
There was a problem hiding this comment.
We should find some other example to use this.
You could use the selector outside of the callback to react to the selection change and show the selected desks.
There was a problem hiding this comment.
But this would not show how the selector can be accessed from inside the callback. Passing in a valid apiRef reference into the callback parameters is the whole purpose of this PR, so your suggestion would not showcase that.
Or did I misunderstand something here?
There was a problem hiding this comment.
WDYT about this?
const handleFilterModelChange = (
model: GridFilterModel,
details: GridCallbackDetails<'filter'>,
) => {
// The row selection is not part of the callback arguments,
// but it can be read from the state through `details.apiRef`
const selectedRows = gridRowSelectionIdsSelector(details.apiRef);
const desks = [...selectedRows.values()].map((row) => row?.desk).filter(Boolean);
if (selectedRows.size === 0) {
setMessage('Filter changed with no rows selected');
} else {
setMessage(
`Filter changed with ${selectedRows.size} selected row(s): ${desks.join(', ')}`,
);
}
};This would set a state value based on the results from a selector inside the callback
There was a problem hiding this comment.
What I meant is that this is not a "good" reason to use selectors in the callback.
You use the selector to update the root state, which defies the whole purpose of the selectors and makes the whole grid re-render because of the inline props.
Claude Opus 5 reviewVerdict: most of the previous round is properly addressed -- generics on
|
| props.signature === GridSignature.DataGridPro || | ||
| props.signature === GridSignature.DataGridPremium | ||
| ? { api: privateApiRef.current.getPublicApi() } | ||
| : {}; |
There was a problem hiding this comment.
was there a reason we didn't provide this for the community version before?
There was a problem hiding this comment.
I honestly don't know... @cherniavskii maybe?
| * The reason for this callback to have been called. | ||
| */ | ||
| reason?: GridControlledStateReasonLookup[K]; | ||
| // TODO: deprecate in v10 |
There was a problem hiding this comment.
we can deprecate now and remove in v10
| newRowSelectionModel: GridRowSelectionModel, | ||
| details: GridCallbackDetails, | ||
| ) => { | ||
| updateSelectedDesks(details); |
There was a problem hiding this comment.
What I meant is that this is not a "good" reason to use selectors in the callback.
You use the selector to update the root state, which defies the whole purpose of the selectors and makes the whole grid re-render because of the inline props.
Addresses review feedback: reactive display of the selection is what selectors outside callbacks are for; the demo now shows reading state that is not part of the callback arguments at event time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dw4bjYohMj5g5QuALoFXoW
Addresses the remaining review findings: - `DataGridPropsWithoutDefaultValue`, `DataGridProPropsWithoutDefaultValue`, the processed props chains, and `GridEventListener` are generic over the public API type (defaulted, never written by consumers), so every callback on every tier now exposes a correctly typed `details.apiRef` - including the community-declared callbacks on Pro/Premium grids and the editing model callbacks - shared feature hooks and shared private-api surfaces reference the props with `Api = any`, concentrating the tier-boundary variance concession in internal signatures instead of per-feature casts; `useGridEvent` accepts `GridEventListener<E, Api>` and bridges to the common event system in one place - `GridCallbackDetails.apiRef` is `Readonly<RefObject<Api>>`, so reassigning `current` is rejected at compile time, matching the getter-based runtime behavior - tests: Pro editing-path callback now asserts a Pro-typed ref, Premium gains coverage for Premium selectors in community-inherited callbacks (control-state, event-based, and editing paths) and a compile-time read-only guard Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dw4bjYohMj5g5QuALoFXoW
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
Both findings from the review are now addressed (6f9b1c5). Type threading is now complete
The ref is now read-only at compile time
Tests
🤖 Generated with Claude Code |
…0808 # Conflicts: # packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts
The six Premium-declared event props kept the default common API type. They now expose a Premium-typed `details.apiRef` like every other callback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dw4bjYohMj5g5QuALoFXoW
LukasTy
left a comment
There was a problem hiding this comment.
Nice work, looks awesome. 👏
Final Claude review and cleanup suggestion
Verdict: mergeable. The remaining round-2 gap is closed -- Api is threaded through GridEventListener and the whole props chain, apiRef is Readonly<RefObject<Api>>, and Premium selectors now compile on every callback kind (control-state, event-based, and editing). Nothing here blocks merge. Two quality items, the first worth doing before merge because it removes 61 files from the diff.
-
The
DataGridProcessedProps<any, any>churn is avoidable with a two-line change. 61 files and 77 occurrences exist only to spell the variance concession at every call site; 53 of the 75 touched package files contain no other change. Set theApidefault toanyonDataGridProcessedPropsandDataGridProProcessedPropsinstead ofGridApiCommunity/GridApiPro, and every bareDataGridProcessedPropsin the internals already means the permissive form. Verified by reverting all 77 occurrences and flipping both defaults:x-data-grid,-pro,-premiumand docs all typecheck clean, Premium selectors still compile inonFilterModelChange/onColumnHeaderClick/onCellModesModelChange/onAggregationModelChange/onRowClick, community typing is unchanged, and a legacyGridEventListener<'cellClick'>handler still assigns on all three tiers. Same concession the PR body describes, stated once instead of 61 times, and a much smaller backport and merge-conflict surface. -
Two type-only assertions sit in a
.test.tsxwhere the callbacks never fire.vitest.shared.mts:128excludes.spec.{js,ts,tsx}from the runner, so type checks belong in a.spec.tsx. Verified by plantingthrow new Error(...)inside both callbacks ingridCallbackDetailsApiRef.DataGridPremium.test.tsx: all 3 tests still pass.should type the apiRef as read-onlyasserts nothing at runtime, and theonCellModesModelChangeblock inshould accept Premium selectors on community-inherited callbacksis dead -- nothing in that test starts cell edit mode. Move both to a.spec.tsx, or trigger the callbacks.
The internals are shared across the three packages. A bare ProcessedProps reference now accepts props typed with any API type. This removes the DataGridProcessedProps<any, any> spelling from every call site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gpgjw7HVWvJj8vxoepBv1n
The test runner excludes .spec files, so type checks belong there. The read-only check and the editing callback check assert nothing at runtime. The runtime test now only keeps callbacks that fire. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gpgjw7HVWvJj8vxoepBv1n
|
I addressed both points from the review:
Typecheck passes for the three grid packages and the docs. The remaining unit tests pass. |
Adds
apiReftoGridCallbackDetails. State selectors can now read the grid state directly inside event callbacks (for exampleonRowSelectionModelChangeoronFilterModelChange). A separateuseGridApiRefis not necessary.publishEventpopulatesGridCallbackDetails.apiReffor all grid variants (Community, Pro, and Premium)publishEventalso receive it (onRowSelectionModelChange,onFilterModelChange,onSortModelChange,onPaginationModelChange,onDetailPanelExpandedRowIdsChange,onRowGroupingModelChange,onAggregationModelChange,onCellModesModelChange,onRowModesModelChange)apiRefis a required field onGridCallbackDetailsand matches its documented usagedetails.apiRefis one stable, read-only ref for each grid instance.publishEventdoes not allocate on the hot path. The type isReadonly<RefObject<Api>>, so an assignment tocurrentis a compile errorGridEventListenercarry a defaultedApitype parameter. Consumers never write it. Every callback on every tier exposes a correctly typedapiRef, including the community-declared callbacks on Pro and Premium gridsApi = any. This keeps the tier-boundary variance concession in internal signatures. The feature code contains no castsGridCallbackDetailsProandGridCallbackDetailsPremiumapiproperty stays for now (marked for deprecation in v10)onFilterModelChangeResolves #20808
🤖 Generated with Claude Code
https://claude.ai/code/session_01Dw4bjYohMj5g5QuALoFXoW