fix(front): reload record board groups when view groups change (#23637)

## Problem

Fixes #23462

On a Kanban (record board) view grouped by a SELECT field, adding a new
option to that field creates the column, but dragging a record into the
new column silently fails (no move, no error) until a hard page reload.

## Root cause

`RecordIndexLoadBaseOnContextStoreEffect` builds its load key from the
view id and the calendar-week flag only:

```
`${contextStoreCurrentViewId}-${isCalendarWeekViewEnabled}`
```

The effect early-returns when `loadedViewKey === currentViewLoadKey`.
When a new `ViewGroup` is created from the added SELECT option, the view
id does not change, so the key is unchanged and `loadRecordIndexStates`
never re-runs. The record group state (`recordGroupIdsComponentState` /
`recordGroupDefinitionFamilyState`) stays stale, so the drop handler
cannot resolve the new group's field value and the move no-ops. A hard
reload fixes it because the view then loads with the new group present
from the start.

## Fix

Include a signature of `view.viewGroups` (ordered
`id:position:isVisible`) in the load key so the effect re-runs
`loadRecordIndexStates` whenever the view's groups change, not only when
the view id changes. The calendar-week flag is kept in the key.

## Testing

Verified end to end on a local instance against an Opportunities "By
Stage" Kanban, with the record's stage change confirmed in the database:

- **Before the fix:** add a new Stage option in-session, then drag a
record into the new column. Dragging into an existing column persists
the move; dragging into the newly created column does nothing (record's
stage unchanged in DB).
- **After the fix:** same flow, dragging a record into the newly created
column moves it and persists the new stage in DB, with no reload.

`nx typecheck twenty-front` and `oxlint` pass.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23637?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:
Thomas Trompette
2026-07-31 15:32:13 +02:00
committed by GitHub
parent 2aa118cb0d
commit 706d72e53e
2 changed files with 52 additions and 4 deletions
@@ -18,16 +18,21 @@ export const RecordIndexLoadBaseOnContextStoreEffect = () => {
FeatureFlagKey.IS_CALENDAR_WEEK_VIEW_ENABLED,
);
const currentViewLoadKey = isDefined(contextStoreCurrentViewId)
? `${contextStoreCurrentViewId}-${isCalendarWeekViewEnabled}`
: undefined;
const [loadedViewKey, setLoadedViewKey] = useState<string | undefined>();
const view = useAtomFamilySelectorValue(viewFromViewIdFamilySelector, {
viewId: contextStoreCurrentViewId ?? '',
});
const viewGroupsSignature = (view?.viewGroups ?? [])
.map((viewGroup) => viewGroup.id)
.sort()
.join(',');
const currentViewLoadKey = isDefined(contextStoreCurrentViewId)
? `${contextStoreCurrentViewId}-${isCalendarWeekViewEnabled}-${viewGroupsSignature}`
: undefined;
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
useEffect(() => {
@@ -84,4 +84,47 @@ describe('RecordIndexLoadBaseOnContextStoreEffect', () => {
FeatureFlagKey.IS_CALENDAR_WEEK_VIEW_ENABLED,
);
});
it('reloads the persisted view state when the view groups change', () => {
const viewWithGroups = {
id: 'view-id',
viewGroups: [{ id: 'group-1' }],
};
useAtomFamilySelectorValueMock.mockReturnValue(viewWithGroups);
const { rerender } = render(<RecordIndexLoadBaseOnContextStoreEffect />);
expect(loadRecordIndexStates).toHaveBeenCalledTimes(1);
rerender(<RecordIndexLoadBaseOnContextStoreEffect />);
expect(loadRecordIndexStates).toHaveBeenCalledTimes(1);
useAtomFamilySelectorValueMock.mockReturnValue({
...viewWithGroups,
viewGroups: [...viewWithGroups.viewGroups, { id: 'group-2' }],
});
rerender(<RecordIndexLoadBaseOnContextStoreEffect />);
expect(loadRecordIndexStates).toHaveBeenCalledTimes(2);
});
it('does not reload when the view groups are only reordered', () => {
useAtomFamilySelectorValueMock.mockReturnValue({
id: 'view-id',
viewGroups: [{ id: 'group-1' }, { id: 'group-2' }],
});
const { rerender } = render(<RecordIndexLoadBaseOnContextStoreEffect />);
expect(loadRecordIndexStates).toHaveBeenCalledTimes(1);
useAtomFamilySelectorValueMock.mockReturnValue({
id: 'view-id',
viewGroups: [{ id: 'group-2' }, { id: 'group-1' }],
});
rerender(<RecordIndexLoadBaseOnContextStoreEffect />);
expect(loadRecordIndexStates).toHaveBeenCalledTimes(1);
});
});