Skip to content

Header-click sorting is a silent no-op for nested field refs in DataGrid columns #68

Description

@vparys

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.tsextractFieldName) 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions