Summary
A DataGrid column declared with a nested field ref (field={it.relation.scalar} through a hasOne — e.g. a scalar on a 1:1 view entity) registers its sortingField as the dotted path, but useSortingState.setOrderBy / directionOf key the toggle by the ref's leaf fieldName — the sortableFields guard never matches, so a header click on such a column is a silent no-op and the sort indicator never lights.
Environment
Reproduction
const it = createCollectorProxy<Project>(new SelectionScope(), 'Project', schemaRegistry)
const nestedRef = it.stats.memberCount // hasOne('ProjectStats') → scalar
extractFieldName(nestedRef) // 'stats.memberCount' — what createColumn
// stores as the column's sortingField
const { result } = renderHook(() =>
useSortingState({ sortableFields: new Set(['stats.memberCount']) }))
act(() => result.current.setOrderBy(nestedRef, 'next')) // ← what a header click dispatches
result.current.resolvedOrderBy // expected [{ stats: { memberCount: 'asc' } }]
// actual undefined (silent no-op)
result.current.directionOf(nestedRef) // expected 'asc', actual null
A top-level ref (it.name) sorts fine, and seeding the same dotted key via initialSorting: { 'stats.memberCount': 'desc' } resolves to the correct nested orderBy [{ stats: { memberCount: 'desc' } }] — both covered as passing controls in the test file. So the dotted path is already the canonical sorting key end to end; only the ref→key derivation in setOrderBy / directionOf disagrees with it.
Expected behavior
Clicking a sortable column header declared with a nested field ref toggles the sort, producing the nested orderBy ([{ stats: { memberCount: 'asc' } }]) — same as DataGridNumberColumn field={it.stats.memberCount} sortable promises, and consistent with initialSorting handling of dotted keys.
Actual behavior
setOrderBy returns early on the sortableFields.has(fieldName) guard, because:
sortableFields contains the DOTTED path: useDataGridSetup.ts:180-181 adds col.sortingField, which createColumn.ts:76,112 derives via extractFieldName = meta.fullPath.join('.') (columns.tsx:67-72);
setOrderBy keys by field[FIELD_REF_META].fieldName — the LEAF segment only (useDataViewState.ts:186-187); directionOf has the same leaf keying (useDataViewState.ts:213), so the header indicator never lights either.
No error is thrown; the click just does nothing.
Suspected root cause
packages/bindx-dataview/src/useDataViewState.ts (useSortingState):
const setOrderBy = useCallback(
<T>(field: FieldRef<T>, action: SortingDirectionAction, append?: boolean): void => {
const fieldName = field[FIELD_REF_META].fieldName // ← leaf only, e.g. 'memberCount'
if (!sortableFields.has(fieldName)) return // ← set contains 'stats.memberCount'
while the registration side (createColumn.ts → extractFieldName) uses fullPath.join('.'). resolvedOrderBy already resolves dotted keys via buildNestedOrderBy (useDataViewState.ts:220,226), so everything downstream of the state expects the dotted form.
Suggested fix
In useSortingState, derive the key the same way the registration side does — e.g. reuse extractFieldName:
const meta = field[FIELD_REF_META]
const fieldName = meta.fullPath && meta.fullPath.length > 0 ? meta.fullPath.join('.') : meta.fieldName
in both setOrderBy and directionOf. Top-level refs are unaffected (fullPath of length 1 joins to the same leaf, or is absent). If persisted sorting state could contain leaf keys written by older code, a fallback lookup on meta.fieldName may be warranted — deferring that call to maintainers.
Workaround shipped downstream
We applied a temporary workaround in our project, marked TODO [BindX] (<this-issue-url>): <description>. The workaround wraps the nested field ref passed to the column so its FIELD_REF_META.fieldName carries the dotted path, making the header-click key match sortableFields; we will remove it once this issue is resolved.
Summary
A DataGrid column declared with a nested field ref (
field={it.relation.scalar}through a hasOne — e.g. a scalar on a 1:1 view entity) registers itssortingFieldas the dotted path, butuseSortingState.setOrderBy/directionOfkey the toggle by the ref's leaffieldName— thesortableFieldsguard never matches, so a header click on such a column is a silent no-op and the sort indicator never lights.Environment
@contember/bindx@0.1.46(version installed in the reporting project)contember/bindx@mainas of3c2fd0dtests/react/dataview/nestedFieldSortingKey.test.tsxbug/datagrid-nested-field-sorting-noopReproduction
A top-level ref (
it.name) sorts fine, and seeding the same dotted key viainitialSorting: { 'stats.memberCount': 'desc' }resolves to the correct nested orderBy[{ stats: { memberCount: 'desc' } }]— both covered as passing controls in the test file. So the dotted path is already the canonical sorting key end to end; only the ref→key derivation insetOrderBy/directionOfdisagrees with it.Expected behavior
Clicking a sortable column header declared with a nested field ref toggles the sort, producing the nested orderBy (
[{ stats: { memberCount: 'asc' } }]) — same asDataGridNumberColumn field={it.stats.memberCount} sortablepromises, and consistent withinitialSortinghandling of dotted keys.Actual behavior
setOrderByreturns early on thesortableFields.has(fieldName)guard, because:sortableFieldscontains the DOTTED path:useDataGridSetup.ts:180-181addscol.sortingField, whichcreateColumn.ts:76,112derives viaextractFieldName=meta.fullPath.join('.')(columns.tsx:67-72);setOrderBykeys byfield[FIELD_REF_META].fieldName— the LEAF segment only (useDataViewState.ts:186-187);directionOfhas the same leaf keying (useDataViewState.ts:213), so the header indicator never lights either.No error is thrown; the click just does nothing.
Suspected root cause
packages/bindx-dataview/src/useDataViewState.ts(useSortingState):while the registration side (
createColumn.ts→extractFieldName) usesfullPath.join('.').resolvedOrderByalready resolves dotted keys viabuildNestedOrderBy(useDataViewState.ts:220,226), so everything downstream of the state expects the dotted form.Suggested fix
In
useSortingState, derive the key the same way the registration side does — e.g. reuseextractFieldName:in both
setOrderByanddirectionOf. Top-level refs are unaffected (fullPathof length 1 joins to the same leaf, or is absent). If persisted sorting state could contain leaf keys written by older code, a fallback lookup onmeta.fieldNamemay be warranted — deferring that call to maintainers.Workaround shipped downstream
We applied a temporary workaround in our project, marked
TODO [BindX] (<this-issue-url>): <description>. The workaround wraps the nested field ref passed to the column so itsFIELD_REF_META.fieldNamecarries the dotted path, making the header-click key matchsortableFields; we will remove it once this issue is resolved.