fix(front): apply advanced filter value and operand edits after a fresh page load (#23819)

## Problem

On a freshly loaded page (e.g. opening a saved view with an advanced
filter), editing an advanced filter rule silently fails:

- Toggling a record in a relation value dropdown (e.g. Account Owner
\`Is Me\` → adding a workspace member) does nothing: the checkbox does
not stick and the filter is never updated.
- Changing the operand (e.g. \`Is\` → \`Is not\`) is also a no-op.

The edits only work in the session where the rule was just created,
which is why this slips through manual testing of new filters.

Found while QAing #23718: the new runtime-computed relation chip made
the stale value visible enough to notice the edit was never applied.

## Root cause

The object-filter-dropdown component states for an advanced filter row
live under the row's instance id (\`advanced-filter-<recordFilterId>\`,
provided by \`AdvancedFilterRecordFilterRow\`). They are hydrated by
\`useSetRecordFilterUsedInAdvancedFilterDropdownRow\` when a rule is
created — but never on a later page load.

\`AdvancedFilterValueInput\` did write
\`objectFilterDropdownCurrentRecordFilter\` & co on dropdown open, but
under a different instance id
(\`advanced-filter-view-filter-value-input-<recordFilterId>\`) that no
dropdown content ever reads — dead writes.

So after a reload, \`selectedOperandInDropdown\` is undefined in the
instance the dropdown reads, and
\`ObjectFilterDropdownRecordSelect.handleMultipleRecordSelectChange\`
(gated on it) silently drops the selection. Same story for
\`useApplyObjectFilterDropdownOperand\`, which sees no current record
filter and never upserts.

## Fix

- \`useSetRecordFilterUsedInAdvancedFilterDropdownRow\` now also
hydrates \`subFieldNameUsedInDropdown\` and
\`relationTargetFieldMetadataIdUsedInDropdown\`, mirroring
\`useSetEditableFilterChipDropdownStates\` (the regular filter chip
flow, which does not have this bug).
- \`AdvancedFilterValueInput\` calls it on value-dropdown open instead
of the phantom-instance writes, and its search-input/subFieldName states
now target the row instance actually read by the dropdown content.
- \`AdvancedFilterRecordFilterOperandSelectContent\` hydrates the same
states on operand-dropdown open.

## Test

Verified locally against a seeded workspace, on a saved view \`Account
Owner Is Me\` reloaded in a fresh session:

- Adding a member in the value dropdown now applies immediately: chip
updates to \`Me, Aaron Munoz\`, results re-query, Update view appears.
- Unchecking \`Me\` leaves \`Aaron Munoz\` with the record-name chip and
the filter applied.
- Changing the operand to \`Is not\` applies (count flipped from
owned-by-me to the complement).
- Regular (non-advanced) filter chips unchanged.

Ran \`lint:diff-with-main\`, \`typecheck\` and the advanced-filter jest
suites.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23819?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Charles Bochet
2026-08-05 17:41:57 +02:00
committed by GitHub
parent f7aab2e988
commit 352d7dda55
3 changed files with 37 additions and 26 deletions
@@ -1,4 +1,5 @@
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand';
@@ -42,12 +43,19 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
const { applyObjectFilterDropdownOperand } =
useApplyObjectFilterDropdownOperand();
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
const handleOperandChange = (operand: ViewFilterOperand) => {
closeDropdown(dropdownId);
applyObjectFilterDropdownOperand(operand);
};
const handleDropdownOpen = () => {
setRecordFilterUsedInAdvancedFilterDropdownRow(filter);
};
const selectedItemId = useAtomComponentStateValue(
selectedItemIdComponentState,
dropdownId,
@@ -111,6 +119,7 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start"
onOpen={handleDropdownOpen}
/>
);
};
@@ -2,11 +2,10 @@ import { AdvancedFilterDropdownFilterInput } from '@/object-record/advanced-filt
import { AdvancedFilterDropdownTextInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput';
import { AdvancedFilterValueInputDropdownButtonClickableSelect } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
import { shouldShowFilterTextInput } from '@/object-record/advanced-filter/utils/shouldShowFilterTextInput';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { relationTargetFieldMetadataIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/relationTargetFieldMetadataIdUsedInDropdownComponentState';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
@@ -31,13 +30,16 @@ export const AdvancedFilterValueInput = ({
}: AdvancedFilterValueInputProps) => {
const dropdownId = `advanced-filter-view-filter-value-input-${recordFilterId}`;
const objectFilterDropdownInstanceId =
getAdvancedFilterObjectFilterDropdownComponentInstanceId(recordFilterId);
const currentRecordFilters = useAtomComponentStateValue(
currentRecordFiltersComponentState,
);
const subFieldNameUsedInDropdown = useAtomComponentStateValue(
subFieldNameUsedInDropdownComponentState,
dropdownId,
objectFilterDropdownInstanceId,
);
const recordFilter = currentRecordFilters.find(
@@ -48,24 +50,11 @@ export const AdvancedFilterValueInput = ({
const setObjectFilterDropdownSearchInput = useSetAtomComponentState(
objectFilterDropdownSearchInputComponentState,
dropdownId,
objectFilterDropdownInstanceId,
);
const setFieldMetadataItemIdUsedInDropdown = useSetAtomComponentState(
fieldMetadataItemIdUsedInDropdownComponentState,
dropdownId,
);
const setRelationTargetFieldMetadataIdUsedInDropdown =
useSetAtomComponentState(
relationTargetFieldMetadataIdUsedInDropdownComponentState,
dropdownId,
);
const setObjectFilterDropdownCurrentRecordFilter = useSetAtomComponentState(
objectFilterDropdownCurrentRecordFilterComponentState,
dropdownId,
);
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
const operandHasNoInput =
recordFilter && !configurableViewFilterOperands.has(recordFilter.operand);
@@ -79,11 +68,7 @@ export const AdvancedFilterValueInput = ({
};
const handleFilterValueDropdownOpen = () => {
setObjectFilterDropdownCurrentRecordFilter(recordFilter);
setFieldMetadataItemIdUsedInDropdown(recordFilter.fieldMetadataId);
setRelationTargetFieldMetadataIdUsedInDropdown(
recordFilter.relationTargetFieldMetadataId ?? null,
);
setRecordFilterUsedInAdvancedFilterDropdownRow(recordFilter);
};
const filterType = recordFilter.type;
@@ -95,7 +80,8 @@ export const AdvancedFilterValueInput = ({
const showFilterTextInputInsteadOfDropdown = shouldShowFilterTextInput({
recordFilter,
subFieldNameUsedInDropdown,
subFieldNameUsedInDropdown:
subFieldNameUsedInDropdown ?? recordFilter.subFieldName,
});
return (
@@ -1,7 +1,9 @@
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { relationTargetFieldMetadataIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/relationTargetFieldMetadataIdUsedInDropdownComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useCallback } from 'react';
import { useStore } from 'jotai';
@@ -35,6 +37,20 @@ export const useSetRecordFilterUsedInAdvancedFilterDropdownRow = () => {
}),
recordFilter,
);
store.set(
subFieldNameUsedInDropdownComponentState.atomFamily({
instanceId: advancedFilterRowObjectFilterDropdownComponentInstanceId,
}),
recordFilter.subFieldName,
);
store.set(
relationTargetFieldMetadataIdUsedInDropdownComponentState.atomFamily({
instanceId: advancedFilterRowObjectFilterDropdownComponentInstanceId,
}),
recordFilter.relationTargetFieldMetadataId ?? null,
);
},
[store],
);