fix(OneFilterPicker): track nested filter keys when options load async - #5270
fix(OneFilterPicker): track nested filter keys when options load async#5270factmarc10010 wants to merge 3 commits into
Conversation
A nested `in` filter only exposed its child filter keys when its options were a literal array, so a filter whose options arrive from a loader resolved to no nested keys at all. The parent then read as empty everywhere it is summarised outside the option list: no active dot in the filter list, no contribution to the "N selected" count or the select-all indeterminate state, nothing cleared by its clear/select-all toggle, and an orphaned child value left behind when its chip is removed. The keys have to be knowable without the options at hand, since the filter list and the chips render before the filter is ever opened. Adds an optional `nestedFilterKeys` to the in-filter options for that, and falls back to the options a previous load already resolved.
🔍 Review policy: Code changeDefault rule: any other change needs one approval from f0-devs (rule 4). Required approvals
How this was decided
Policy source: |
✅ No untranslated copy addedEvery user-visible string in this PR comes from the i18n layer. Codebase total unchanged at 133. |
✅ No New Circular DependenciesNo new circular dependencies detected. Current count: 0 |
✅ Storybook docs — no pages lostEvery page reachable on Links point at this PR's Storybook build — browse the full Storybook. Snapshot of the Storybook index (docs pages + stories) compared against |
📦 Alpha Package Version PublishedUse Use |
🔍 Visual review for your branch is published 🔍Here are the links to: |
♿ Accessibility (axe) — components changed in this PR✅ No a11y issues in the stories this PR changed. Scope: only stories in the files/component folders this PR changed. It can't yet flag downstream ripple from shared-code/token changes, or diff against |
✅ No breaking public API changesNo public exports were removed, renamed, or had existing props/types changed in a breaking way compared to Comparing
|
albertpmz
left a comment
There was a problem hiding this comment.
Nice work, let's check this issue below
| const resolvedOptions = | ||
| "options" in filterOptions && Array.isArray(filterOptions.options) | ||
| ? filterOptions.options | ||
| : getCachedOptions<T>(getCacheKey(schema)) |
There was a problem hiding this comment.
This fallback doesn't fire where it's needed. With async options and no nestedFilterKeys, the dot stays off after the options load and only appears on a later mount: nestedKeysMap in FilterList.tsx is a useMemo keyed on definition, so the cache filling later never invalidates it. Same filter, two behaviors, depending on history the user can't see. It also makes render impure, since this reads a mutable module-level Map during render.
Suggest dropping the fallback and letting nestedFilterKeys be the single answer for non-literal options. It's deterministic and already carries the fix on its own: all four integration tests pass on the declared keys alone.
Heads up that option-utils.test.ts:76 can't catch this. It calls loadOptions(getCacheKey(schema), ...) with the same object and asserts the read back, so it covers the Map round-trip, not the component path.
Problem
A nested
infilter exposed its child filter keys only when itsoptionswere a literal array. Any filter that loads its options from a function (or asource) resolved to no nested keys, so the picker treated the parent as empty everywhere it is summarised outside the option list:FilterListInFilterInFilterOneFilterPicker.removeFilterValuegetClearedFiltersValueRepro in the product: People → Filters → Workplace, select only a work area under an office. The office row gets its dot (that path reads the already-loaded options), the Workplace entry in the filter list does not.
Fix
The nested keys have to be knowable without the options at hand, because the filter list and the chips render before the filter is ever opened — and still render that way after a reload with filters in the URL.
nestedFilterKeyson the in-filter options, declaring the child keys up front. Authoritative and always available, including forsource-backed options.collectNestedFilterKeysnow takes the whole schema, so whenoptionsis not a literal array it can also fall back to the options a previous load already resolved.InFilterthe keys additionally union with the currently loaded options, so they pick up children that were still unresolved when the schema was read.Consumers with literal arrays keep working untouched;
nestedFilterKeysis only needed for async/sourceoptions.Testing
packages/react/src/patterns/OneFilterPicker/__test__/nestedAsyncOptions.test.tsx— nested filter with async options, covering the four behaviours above end to end. All four fail onmain..../InFilter/components/__tests__/option-utils.test.ts— unit coverage for the three key sources (declared, literal, resolved-from-cache) and their merge.The existing nested tests all use literal arrays, which is why this slipped through.
Ref: https://factorialmakers.atlassian.net/browse/FCT-61539