Fix record title cell reopening on every page refresh (#23551)

## What

`location.state.isNewRecord` is set when navigating to a freshly created
record so the title cell opens for naming. But router state lives in
**browser history state, which survives page refreshes** — so every
refresh of that record's page re-opens the title cell with an empty
draft and a blinking cursor.

Strip the flag after its one intended consumption in `PageChangeEffect`
(react-router keeps user state under `history.state.usr`).

## Repro (on current main, any view set to open records in record page —
or on mobile)

1. Create a record from a table; you land on its record page with the
title focused (intended).
2. Name it, click away, then refresh the page.
3. The title cell re-opens, empty, focused — on every refresh, forever.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23551?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. -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
avonian
2026-07-31 14:48:23 +02:00
committed by GitHub
parent 57ecab8571
commit d02332834b
7 changed files with 73 additions and 67 deletions
@@ -19,6 +19,7 @@ import { useResetTableRowSelection } from '@/object-record/record-table/hooks/in
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { useOpenNewRecordTitleCell } from '@/object-record/record-title-cell/hooks/useOpenNewRecordTitleCell';
import { newRecordTitleCellToOpenState } from '@/object-record/record-title-cell/states/newRecordTitleCellToOpenState';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { PageFocusId } from '@/types/PageFocusId';
import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem';
@@ -206,7 +207,6 @@ export const PageChangeEffect = () => {
break;
}
case isMatchingLocation(location, AppPath.RecordShowPage): {
const isNewRecord = location.state?.isNewRecord === true;
const isSidePanelOpen = store.get(isSidePanelOpenedState.atom);
if (!isSidePanelOpen) {
@@ -225,14 +225,21 @@ export const PageChangeEffect = () => {
});
}
if (
isNewRecord &&
isDefined(location.state?.labelIdentifierFieldName)
) {
openNewRecordTitleCell({
recordId: location.state.objectRecordId,
fieldName: location.state.labelIdentifierFieldName,
});
const newRecordTitleCellToOpen = store.get(
newRecordTitleCellToOpenState.atom,
);
if (isDefined(newRecordTitleCellToOpen)) {
const objectRecordIdFromPath = matchPath(
AppPath.RecordShowPage,
location.pathname,
)?.params.objectRecordId;
if (newRecordTitleCellToOpen.recordId === objectRecordIdFromPath) {
openNewRecordTitleCell(newRecordTitleCellToOpen);
}
store.set(newRecordTitleCellToOpenState.atom, null);
}
break;
}
@@ -17,15 +17,10 @@ export const ComposeCampaignCommand = () => {
await createMessageCampaign({ id: campaignId });
navigateApp(
AppPath.RecordShowPage,
{
objectNameSingular: CoreObjectNameSingular.MessageCampaign,
objectRecordId: campaignId,
},
undefined,
{ state: { isNewRecord: true, objectRecordId: campaignId } },
);
navigateApp(AppPath.RecordShowPage, {
objectNameSingular: CoreObjectNameSingular.MessageCampaign,
objectRecordId: campaignId,
});
};
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} ready />;
@@ -9,6 +9,7 @@ import { useResolveOpenRecordIn } from '@/object-record/record-index/hooks/useRe
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useBuildRecordInputFromFilters } from '@/object-record/record-table/hooks/useBuildRecordInputFromFilters';
import { newRecordTitleCellToOpenState } from '@/object-record/record-title-cell/states/newRecordTitleCellToOpenState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
@@ -101,22 +102,18 @@ export const useCreateNewIndexRecord = ({
const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
if (isDefined(labelIdentifierFieldMetadataItem)) {
store.set(newRecordTitleCellToOpenState.atom, {
recordId,
fieldName: labelIdentifierFieldMetadataItem.name,
});
}
closeSidePanelMenu();
navigate(
AppPath.RecordShowPage,
{
objectNameSingular: objectMetadataItem.nameSingular,
objectRecordId: recordId,
},
undefined,
{
state: {
isNewRecord: true,
objectRecordId: recordId,
labelIdentifierFieldName: labelIdentifierFieldMetadataItem?.name,
},
},
);
navigate(AppPath.RecordShowPage, {
objectNameSingular: objectMetadataItem.nameSingular,
objectRecordId: recordId,
});
}
if (isDefined(recordIndexGroupFieldMetadataItem)) {
@@ -0,0 +1,8 @@
import { type NewRecordTitleCellToOpen } from '@/object-record/record-title-cell/types/NewRecordTitleCellToOpen';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const newRecordTitleCellToOpenState =
createAtomState<NewRecordTitleCellToOpen | null>({
key: 'record-title-cell/newRecordTitleCellToOpenState',
defaultValue: null,
});
@@ -0,0 +1,4 @@
export type NewRecordTitleCellToOpen = {
recordId: string;
fieldName: string;
};
@@ -7,6 +7,7 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { newRecordTitleCellToOpenState } from '@/object-record/record-title-cell/states/newRecordTitleCellToOpenState';
import { getDefaultRecordPageLayoutId } from '@/page-layout/utils/getDefaultRecordPageLayoutId';
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
import { SIDE_PANEL_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelComponentInstanceId';
@@ -133,6 +134,7 @@ describe('useOpenRecordInSidePanel', () => {
beforeEach(() => {
jest.clearAllMocks();
mockIsMobile = false;
jotaiStore.set(newRecordTitleCellToOpenState.atom, null);
});
it('should set the correct states and navigate to the record page', () => {
@@ -284,13 +286,12 @@ describe('useOpenRecordInSidePanel', () => {
});
});
expect(mockNavigateApp).toHaveBeenCalledWith(
AppPath.RecordShowPage,
{ objectNameSingular: 'person', objectRecordId: 'record-123' },
undefined,
undefined,
);
expect(mockNavigateApp).toHaveBeenCalledWith(AppPath.RecordShowPage, {
objectNameSingular: 'person',
objectRecordId: 'record-123',
});
expect(mockNavigateSidePanel).not.toHaveBeenCalled();
expect(jotaiStore.get(newRecordTitleCellToOpenState.atom)).toBeNull();
});
it('should forward new record state to the record page on mobile', () => {
@@ -305,20 +306,16 @@ describe('useOpenRecordInSidePanel', () => {
});
});
expect(mockNavigateApp).toHaveBeenCalledWith(
AppPath.RecordShowPage,
{ objectNameSingular: 'person', objectRecordId: 'new-record-123' },
undefined,
{
state: {
isNewRecord: true,
objectRecordId: 'new-record-123',
labelIdentifierFieldName: getLabelIdentifierFieldMetadataItem(
personMockObjectMetadataItem,
)?.name,
},
},
);
expect(mockNavigateApp).toHaveBeenCalledWith(AppPath.RecordShowPage, {
objectNameSingular: 'person',
objectRecordId: 'new-record-123',
});
expect(jotaiStore.get(newRecordTitleCellToOpenState.atom)).toEqual({
recordId: 'new-record-123',
fieldName: getLabelIdentifierFieldMetadataItem(
personMockObjectMetadataItem,
)?.name,
});
expect(mockOpenNewRecordTitleCell).not.toHaveBeenCalled();
});
@@ -14,6 +14,7 @@ import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorF
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { viewableRecordIdState } from '@/object-record/record-side-panel/states/viewableRecordIdState';
import { useOpenNewRecordTitleCell } from '@/object-record/record-title-cell/hooks/useOpenNewRecordTitleCell';
import { newRecordTitleCellToOpenState } from '@/object-record/record-title-cell/states/newRecordTitleCellToOpenState';
import { setRecordPageActiveTabId } from '@/page-layout/utils/setRecordPageActiveTabId';
import {
AppPath,
@@ -79,22 +80,19 @@ export const useOpenRecordInSidePanel = () => {
? getLabelIdentifierFieldMetadataItem(objectMetadataItemForRecordPage)
: undefined;
if (isNewRecord && isDefined(labelIdentifierField)) {
store.set(newRecordTitleCellToOpenState.atom, {
recordId,
fieldName: labelIdentifierField.name,
});
}
closeSidePanelMenu();
navigate(
AppPath.RecordShowPage,
{ objectNameSingular, objectRecordId: recordId },
undefined,
isNewRecord
? {
state: {
isNewRecord: true,
objectRecordId: recordId,
labelIdentifierFieldName: labelIdentifierField?.name,
},
}
: undefined,
);
navigate(AppPath.RecordShowPage, {
objectNameSingular,
objectRecordId: recordId,
});
return;
}