Start Jotai Migration (#17893)

## Recoil → Jotai progressive migration: infrastructure +
ChipFieldDisplay

### Benchmark

In the beginning, there was no hope:
<img width="1180" height="948" alt="image"
src="https://github.com/user-attachments/assets/f8635991-52e6-4958-8240-6ba7214132b2"
/>

Then the hope was reborn
<img width="2070" height="948" alt="image"
src="https://github.com/user-attachments/assets/be1182b9-1c8d-4fdc-ab4c-1484ad74449d"
/>



### Approach

We introduce a **V2 state management layer** backed by Jotai that
mirrors the existing Recoil API, enabling component-by-component
migration without a big-bang rewrite.

#### V2 API (Jotai-backed, Recoil-ergonomic)

- `createStateV2` / `createFamilyStateV2` — drop-in replacements for
`createState` / `createFamilyState`, returning wrapper types over Jotai
atoms
- `useRecoilValueV2`, `useRecoilStateV2`, `useFamilyRecoilValueV2`, etc.
— thin wrappers around Jotai's `useAtomValue` / `useAtom` / `useSetAtom`
- A shared `jotaiStore` (via `createStore()`) passed to a
`<JotaiProvider>` wrapping `<RecoilRoot>`, also accessible imperatively
for dual-writes

#### Dual-write bridge for progressive migration

For state shared between migrated and non-migrated components, we use
**dual-write**: writers update both the Recoil atom and the Jotai V2
atom (via `jotaiStore.set()`). This avoids sync components or extra
subscriptions.

Write sites updated: `useUpsertRecordsInStore`, `useSetRecordTableData`,
`ListenRecordUpdatesEffect`, `RecordShowEffect`,
`useLoadRecordIndexStates`, `useUpdateObjectViewOptions`.

#### First migration: ChipFieldDisplay render path

- `useChipFieldDisplay` → reads `recordStoreFamilyStateV2` via
`useFamilyRecoilValueV2` (was `useRecoilValue(recordStoreFamilyState)`)
- `RecordChip` → reads `recordIndexOpenRecordInStateV2` via
`useRecoilValueV2` (was `useRecoilValue(recordIndexOpenRecordInState)`)
- `Avatar` (twenty-ui) and event handlers (`useOpenRecordInCommandMenu`)
left on Recoil — not on the render path / in a different package

#### Pattern for migrating additional state

1. Create V2 atom: `createStateV2` or `createFamilyStateV2`
2. Add `jotaiStore.set(v2Atom, value)` at each write site
3. Switch readers to `useRecoilValueV2(v2Atom)`
4. Once all readers are migrated, remove the Recoil atom and dual-writes

#### Why not jotai-recoil-adapter?

Evaluated
[jotai-recoil-adapter](https://github.com/clockelliptic/jotai-recoil-adapter)
— not production-ready (21 open issues, no React 19, forces providerless
mode, missing types). We built a purpose-built thin layer instead.
This commit is contained in:
Charles Bochet
2026-02-12 16:05:38 +01:00
committed by GitHub
parent 08b962b0d2
commit d2f8352cb8
87 changed files with 1086 additions and 241 deletions
@@ -2,13 +2,13 @@ import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordIn
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { t } from '@lingui/core/macro';
import { type MouseEvent } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import {
AvatarChip,
@@ -55,7 +55,9 @@ export const RecordChip = ({
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const recordIndexOpenRecordIn = useRecoilValueV2(
recordIndexOpenRecordInStateV2,
);
const canOpenInSidePanel = canOpenObjectInSidePanel(objectNameSingular);
const isSidePanelViewOpenRecordInType =
@@ -1,5 +1,7 @@
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
import { type GraphQLView } from '@/views/types/GraphQLView';
import { type ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@@ -27,6 +29,7 @@ export const useUpdateObjectViewOptions = () => {
(openRecordIn: ViewOpenRecordInType, view: GraphQLView | undefined) => {
if (!view) return;
setRecordIndexOpenRecordIn(openRecordIn);
jotaiStore.set(recordIndexOpenRecordInStateV2.atom, openRecordIn);
updateCurrentView({
openRecordIn,
});
@@ -1,20 +1,18 @@
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { useFilesFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useFilesFieldDisplay';
import { filesFieldUploadState } from '@/object-record/record-field/ui/states/filesFieldUploadState';
import { filesFieldUploadStateV2 } from '@/object-record/record-field/ui/states/filesFieldUploadStateV2';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { FilesDisplay } from '@/ui/field/display/components/FilesDisplay';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
export const FilesFieldDisplay = () => {
const { recordId, fieldDefinition } = useContext(FieldContext);
const { fieldValue, disableChipClick } = useFilesFieldDisplay();
const uploadState = useRecoilValue(
filesFieldUploadState({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
}),
);
const uploadState = useFamilyRecoilValueV2(filesFieldUploadStateV2, {
recordId,
fieldName: fieldDefinition.metadata.fieldName,
});
const isUploadWindowOpen = uploadState === 'UPLOAD_WINDOW_OPEN';
const isFileUploading = uploadState === 'UPLOADING_FILE';
@@ -3,7 +3,7 @@ import { useContext } from 'react';
import { type FieldActorValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { AuthContext } from '@/auth/contexts/AuthContext';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { isDefined } from 'twenty-shared/utils';
import { type WorkspaceMember } from '~/generated-metadata/graphql';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -21,7 +21,7 @@ export const useActorFieldDisplay = (): ActorFieldDisplayValue | undefined => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldActorValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldActorValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { type FieldAddressValue } from '@/object-record/record-field/ui/types/FieldMetadata';
@@ -9,7 +9,7 @@ export const useAddressFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldAddressValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldAddressValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -4,7 +4,7 @@ import {
type FieldArrayMetadata,
type FieldArrayValue,
} from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { useContext } from 'react';
@@ -13,7 +13,7 @@ export const useArrayFieldDisplay = () => {
const { fieldName } = fieldDefinition.metadata;
const fieldValue = useRecordFieldValue<FieldArrayValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldArrayValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useBooleanFieldDisplay = () => {
@@ -8,7 +8,7 @@ export const useBooleanFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<boolean | undefined>(
const fieldValue = useRecordFieldValueV2<boolean | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -5,11 +5,11 @@ import { isFieldText } from '@/object-record/record-field/ui/types/guards/isFiel
import { isFieldUuid } from '@/object-record/record-field/ui/types/guards/isFieldUuid';
import { isNonEmptyString } from '@sniptt/guards';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { isFieldActor } from '@/object-record/record-field/ui/types/guards/isFieldActor';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { isDefined } from 'twenty-shared/utils';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -50,7 +50,10 @@ export const useChipFieldDisplay = () => {
? fieldDefinition.metadata.objectMetadataNameSingular
: undefined;
const recordValue = useRecoilValue(recordStoreFamilyState(recordId));
const recordValue = useFamilyRecoilValueV2(
recordStoreFamilyStateV2,
recordId,
);
if (!isNonEmptyString(objectNameSingular)) {
throw new Error('Object metadata name singular is not a non-empty string');
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldCurrency } from '@/object-record/record-field/ui/types/guards/isFieldCurrency';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { type FieldCurrencyValue } from '@/object-record/record-field/ui/types/FieldMetadata';
@@ -18,7 +18,7 @@ export const useCurrencyFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldCurrencyValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldCurrencyValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { type FieldDateMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useDateFieldDisplay = () => {
@@ -10,7 +10,7 @@ export const useDateFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<string | undefined>(
const fieldValue = useRecordFieldValueV2<string | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { type FieldDateTimeMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useDateTimeFieldDisplay = () => {
@@ -10,7 +10,7 @@ export const useDateTimeFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<string | undefined>(
const fieldValue = useRecordFieldValueV2<string | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -3,7 +3,7 @@ import { useContext } from 'react';
import { type FieldEmailsValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldEmails } from '@/object-record/record-field/ui/types/guards/isFieldEmails';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -14,7 +14,7 @@ export const useEmailsFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldEmailsValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldEmailsValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -4,7 +4,7 @@ import {
type FieldFilesMetadata,
type FieldFilesValue,
} from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { useContext } from 'react';
@@ -14,7 +14,7 @@ export const useFilesFieldDisplay = () => {
const { fieldName } = fieldDefinition.metadata;
const fieldValue = useRecordFieldValue<FieldFilesValue[] | undefined>(
const fieldValue = useRecordFieldValueV2<FieldFilesValue[] | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import { type FieldFullNameValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useFullNameFieldDisplay = () => {
@@ -10,7 +10,7 @@ export const useFullNameFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldFullNameValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldFullNameValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -3,7 +3,7 @@ import { useContext } from 'react';
import { type FieldJsonValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useFormattedJsonFieldValue } from '@/object-record/record-field/ui/meta-types/hooks/useFormattedJsonFieldValue';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useJsonFieldDisplay = () => {
@@ -12,7 +12,7 @@ export const useJsonFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldJsonValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldJsonValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -4,7 +4,7 @@ import { type FieldLinksValue } from '@/object-record/record-field/ui/types/Fiel
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldLinks } from '@/object-record/record-field/ui/types/guards/isFieldLinks';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -15,7 +15,7 @@ export const useLinksFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldLinksValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldLinksValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -7,7 +7,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -32,7 +32,7 @@ export const useMorphRelationFromManyFieldDisplay = () => {
const button = fieldDefinition.editButtonIcon;
const morphValuesWithObjectNameSingular = useRecordFieldValue<
const morphValuesWithObjectNameSingular = useRecordFieldValueV2<
{
objectNameSingular: string;
value: ObjectRecord;
@@ -11,7 +11,7 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from 'twenty-shared/utils';
@@ -34,7 +34,7 @@ export const useMorphRelationToOneFieldDisplay = () => {
const button = fieldDefinition.editButtonIcon;
const morphFieldValueWithObjectName = useRecordFieldValue<{
const morphFieldValueWithObjectName = useRecordFieldValueV2<{
objectNameSingular: string;
value: ObjectRecord;
}>(recordId, fieldDefinition.metadata.fieldName, fieldDefinition);
@@ -6,14 +6,14 @@ import {
type FieldMultiSelectMetadata,
type FieldMultiSelectValue,
} from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
export const useMultiSelectFieldDisplay = () => {
const { recordId, fieldDefinition } = useContext(FieldContext);
const { fieldName } = fieldDefinition.metadata;
const fieldValue = useRecordFieldValue<FieldMultiSelectValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldMultiSelectValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldNumber } from '@/object-record/record-field/ui/types/guards/isFieldNumber';
@@ -13,7 +13,7 @@ export const useNumberFieldDisplay = () => {
assertFieldMetadata(FieldMetadataType.NUMBER, isFieldNumber, fieldDefinition);
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<number | null>(
const fieldValue = useRecordFieldValueV2<number | null>(
recordId,
fieldName,
fieldDefinition,
@@ -4,7 +4,7 @@ import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/Fie
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldPhones } from '@/object-record/record-field/ui/types/guards/isFieldPhones';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -15,7 +15,7 @@ export const usePhonesFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldPhonesValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldPhonesValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { type FieldRatingValue } from 'twenty-shared/types';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -9,7 +9,7 @@ export const useRatingFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldRatingValue>(
const fieldValue = useRecordFieldValueV2<FieldRatingValue>(
recordId,
fieldName,
fieldDefinition,
@@ -8,7 +8,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { isDefined } from 'twenty-shared/utils';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
@@ -35,7 +35,7 @@ export const useRelationFromManyFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<ObjectRecord[] | undefined>(
const fieldValue = useRecordFieldValueV2<ObjectRecord[] | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -12,7 +12,7 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { getJoinColumnNameOrThrow } from '@/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { isDefined } from 'twenty-shared/utils';
export const useRelationToOneFieldDisplay = () => {
@@ -36,7 +36,7 @@ export const useRelationToOneFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<ObjectRecord | undefined>(
const fieldValue = useRecordFieldValueV2<ObjectRecord | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -46,7 +46,7 @@ export const useRelationToOneFieldDisplay = () => {
fieldDefinition.metadata.settings,
);
const foreignKeyFieldValue = useRecordFieldValue<string | null | undefined>(
const foreignKeyFieldValue = useRecordFieldValueV2<string | null | undefined>(
recordId,
joinColumnName,
{ type: FieldMetadataType.UUID, metadata: { fieldName: joinColumnName } },
@@ -3,7 +3,7 @@ import { useContext } from 'react';
import { type FieldRichTextValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import type { PartialBlock } from '@blocknote/core';
import { isDefined, parseJson } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -20,7 +20,7 @@ export const useRichTextFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldRichTextValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldRichTextValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -3,7 +3,7 @@ import { useContext } from 'react';
import { type FieldRichTextV2Value } from '@/object-record/record-field/ui/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -18,7 +18,7 @@ export const useRichTextV2FieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldRichTextV2Value | undefined>(
const fieldValue = useRecordFieldValueV2<FieldRichTextV2Value | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import {
type FieldSelectMetadata,
@@ -14,7 +14,7 @@ export const useSelectFieldDisplay = () => {
const { fieldName } = fieldDefinition.metadata;
const fieldValue = useRecordFieldValue<FieldSelectValue | undefined>(
const fieldValue = useRecordFieldValueV2<FieldSelectValue | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useTextFieldDisplay = () => {
@@ -10,7 +10,7 @@ export const useTextFieldDisplay = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue =
useRecordFieldValue<string | undefined>(
useRecordFieldValueV2<string | undefined>(
recordId,
fieldName,
fieldDefinition,
@@ -1,12 +1,12 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { type FieldUUidValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldTextValue } from '@/object-record/record-field/ui/types/guards/isFieldTextValue';
import { isFieldUuid } from '@/object-record/record-field/ui/types/guards/isFieldUuid';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { recordStoreFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFamilySelectorV2';
import { useFamilySelectorStateV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorStateV2';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useUuidField = () => {
@@ -16,13 +16,14 @@ export const useUuidField = () => {
const fieldName = fieldDefinition.metadata.fieldName;
const [fieldValue, setFieldValue] = useRecoilState<FieldUUidValue>(
recordStoreFamilySelector({
recordId,
fieldName: fieldName,
}),
const [fieldValue, setFieldValue] = useFamilySelectorStateV2(
recordStoreFamilySelectorV2,
{ recordId, fieldName },
);
const fieldTextValue = isFieldTextValue(fieldValue) ? fieldValue : '';
const fieldTextValue = isFieldTextValue(fieldValue as FieldUUidValue)
? (fieldValue as FieldUUidValue)
: '';
return {
fieldDefinition,
@@ -1,4 +1,4 @@
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
import { isAttachmentPreviewEnabledStateV2 } from '@/client-config/states/isAttachmentPreviewEnabledStateV2';
import { useFileUpload } from '@/file-upload/hooks/useFileUpload';
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
import { useFilesField } from '@/object-record/record-field/ui/meta-types/hooks/useFilesField';
@@ -11,11 +11,12 @@ import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/re
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { filesSchema } from '@/object-record/record-field/ui/types/guards/isFieldFilesValue';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { filePreviewState } from '@/ui/field/display/states/filePreviewState';
import { filePreviewStateV2 } from '@/ui/field/display/states/filePreviewStateV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useLingui } from '@lingui/react/macro';
import { useCallback, useContext, useMemo, useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { MULTI_ITEM_FIELD_DEFAULT_MAX_VALUES } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -27,9 +28,9 @@ export const FilesFieldInput = () => {
const { t } = useLingui();
const [isUploading, setIsUploading] = useState(false);
const { enqueueErrorSnackBar } = useSnackBar();
const setFilePreview = useSetRecoilState(filePreviewState);
const isAttachmentPreviewEnabled = useRecoilValue(
isAttachmentPreviewEnabledState,
const setFilePreview = useSetRecoilStateV2(filePreviewStateV2);
const isAttachmentPreviewEnabled = useRecoilValueV2(
isAttachmentPreviewEnabledStateV2,
);
const { onEscape, onClickOutside, onEnter } = useContext(
@@ -2,8 +2,10 @@ import { useFileUpload } from '@/file-upload/hooks/useFileUpload';
import { useUploadFilesFieldFile } from '@/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile';
import { uploadMultipleFiles } from '@/object-record/record-field/ui/meta-types/utils/uploadMultipleFiles';
import { filesFieldUploadState } from '@/object-record/record-field/ui/states/filesFieldUploadState';
import { filesFieldUploadStateV2 } from '@/object-record/record-field/ui/states/filesFieldUploadStateV2';
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { RECORD_TABLE_CELL_INPUT_ID_PREFIX } from '@/object-record/record-table/constants/RecordTableCellInputIdPrefix';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState';
@@ -99,6 +101,10 @@ export const useOpenFilesFieldInput = () => {
filesFieldUploadState({ recordId, fieldName }),
'UPLOAD_WINDOW_OPEN',
);
jotaiStore.set(
filesFieldUploadStateV2.atomFamily({ recordId, fieldName }),
'UPLOAD_WINDOW_OPEN',
);
openFileUpload({
multiple: true,
@@ -109,6 +115,10 @@ export const useOpenFilesFieldInput = () => {
});
set(filesFieldUploadState({ recordId, fieldName }), null);
jotaiStore.set(
filesFieldUploadStateV2.atomFamily({ recordId, fieldName }),
null,
);
if (isTableContext && isDefined(recordTableId)) {
set(
@@ -131,6 +141,10 @@ export const useOpenFilesFieldInput = () => {
filesFieldUploadState({ recordId, fieldName }),
'UPLOADING_FILE',
);
jotaiStore.set(
filesFieldUploadStateV2.atomFamily({ recordId, fieldName }),
'UPLOADING_FILE',
);
try {
const uploadedFiles = await uploadMultipleFiles(
@@ -146,6 +160,10 @@ export const useOpenFilesFieldInput = () => {
}
} finally {
set(filesFieldUploadState({ recordId, fieldName }), null);
jotaiStore.set(
filesFieldUploadStateV2.atomFamily({ recordId, fieldName }),
null,
);
if (isTableContext && isDefined(recordTableId)) {
set(
@@ -165,6 +183,10 @@ export const useOpenFilesFieldInput = () => {
},
onCancel: () => {
set(filesFieldUploadState({ recordId, fieldName }), null);
jotaiStore.set(
filesFieldUploadStateV2.atomFamily({ recordId, fieldName }),
null,
);
if (isTableContext && isDefined(recordTableId)) {
set(
@@ -0,0 +1,16 @@
import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2';
type FilesFieldUploadStateKey = {
recordId: string;
fieldName: string;
};
type FilesFieldUploadState = 'UPLOAD_WINDOW_OPEN' | 'UPLOADING_FILE' | null;
export const filesFieldUploadStateV2 = createFamilyStateV2<
FilesFieldUploadState,
FilesFieldUploadStateKey
>({
key: 'filesFieldUploadStateV2',
defaultValue: null,
});
@@ -13,6 +13,8 @@ import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/s
import { recordIndexGroupAggregateFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateFieldMetadataItemComponentState';
import { recordIndexGroupAggregateOperationComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateOperationComponentState';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
@@ -200,6 +202,7 @@ export const useLoadRecordIndexStates = () => {
setRecordIndexViewType(view.type);
setRecordIndexOpenRecordIn(view.openRecordIn);
jotaiStore.set(recordIndexOpenRecordInStateV2.atom, view.openRecordIn);
setRecordIndexCalendarFieldMetadataIdState(
view.calendarFieldMetadataId ?? null,
@@ -0,0 +1,8 @@
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
export const recordIndexOpenRecordInStateV2 =
createStateV2<ViewOpenRecordInType>({
key: 'recordIndexOpenRecordInStateV2',
defaultValue: ViewOpenRecordInType.SIDE_PANEL,
});
@@ -3,7 +3,9 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useEffect } from 'react';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -42,6 +44,10 @@ export const RecordShowEffect = ({
if (JSON.stringify(previousRecordValue) !== JSON.stringify(newRecord)) {
set(recordStoreFamilyState(recordId), newRecord);
jotaiStore.set(
recordStoreFamilyStateV2.atomFamily(recordId),
newRecord,
);
}
},
[recordId],
@@ -0,0 +1,22 @@
import { useAtomValue } from 'jotai';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { recordStoreFieldValueSelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFieldValueSelectorV2';
export const useRecordFieldValueV2 = <T extends unknown>(
recordId: string,
fieldName: string,
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
) => {
const fieldValueAtom = recordStoreFieldValueSelectorV2({
recordId,
fieldName,
fieldDefinition: {
type: fieldDefinition.type,
metadata: fieldDefinition.metadata,
},
});
return useAtomValue(fieldValueAtom) as T | undefined;
};
@@ -3,7 +3,9 @@ import { useRecoilCallback } from 'recoil';
import { filterRecordOnGqlFields } from '@/object-record/cache/utils/filterRecordOnGqlFields';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { isDefined } from 'twenty-shared/utils';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -29,11 +31,16 @@ export const useUpsertRecordsInStore = () => {
: partialRecord;
if (!isDefined(currentRecord)) {
set(recordStoreFamilyState(partialRecord.id), {
const newRecord = {
id: partialRecord.id,
__typename: partialRecord.__typename,
...filteredPartialRecord,
});
};
set(recordStoreFamilyState(partialRecord.id), newRecord);
jotaiStore.set(
recordStoreFamilyStateV2.atomFamily(partialRecord.id),
newRecord,
);
continue;
}
@@ -45,10 +52,15 @@ export const useUpsertRecordsInStore = () => {
: currentRecord;
if (!isDeeplyEqual(filteredCurrentRecord, filteredPartialRecord)) {
set(recordStoreFamilyState(partialRecord.id), {
const updatedRecord = {
...currentRecord,
...filteredPartialRecord,
});
};
set(recordStoreFamilyState(partialRecord.id), updatedRecord);
jotaiStore.set(
recordStoreFamilyStateV2.atomFamily(partialRecord.id),
updatedRecord,
);
}
}
},
@@ -0,0 +1,10 @@
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2';
export const recordStoreFamilyStateV2 = createFamilyStateV2<
ObjectRecord | null | undefined,
string
>({
key: 'recordStoreFamilyStateV2',
defaultValue: null,
});
@@ -0,0 +1,23 @@
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { createWritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createWritableFamilySelectorV2';
export const recordStoreFamilySelectorV2 = createWritableFamilySelectorV2<
unknown,
{ recordId: string; fieldName: string }
>({
key: 'recordStoreFamilySelectorV2',
get:
({ recordId, fieldName }) =>
({ get }) =>
get(recordStoreFamilyStateV2, recordId)?.[fieldName],
set:
({ recordId, fieldName }) =>
({ set }, newValue) => {
set(recordStoreFamilyStateV2, recordId, (prev) =>
prev
? { ...prev, [fieldName]: newValue }
: ({ [fieldName]: newValue } as ObjectRecord),
);
},
});
@@ -0,0 +1,117 @@
import { atom, type Atom } from 'jotai';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2';
import { RelationType, type ObjectRecord } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
const simpleFieldValueSelector = createFamilySelectorV2<
unknown,
{ recordId: string; fieldName: string }
>({
key: 'recordStoreSimpleFieldValue',
get:
({ recordId, fieldName }) =>
({ get }) =>
get(recordStoreFamilyStateV2, recordId)?.[fieldName],
});
const morphAtomCache = new Map<string, Atom<unknown>>();
const getMorphRelationFieldValueAtom = (
recordId: string,
fieldName: string,
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
): Atom<unknown> => {
const cacheKey = `morph__${recordId}__${fieldName}`;
const existing = morphAtomCache.get(cacheKey);
if (existing !== undefined) {
return existing;
}
const morphRelations =
(
fieldDefinition.metadata as {
morphRelations?: Array<{
type: string;
sourceFieldMetadata: { name: string };
targetObjectMetadata: {
nameSingular: string;
namePlural: string;
};
}>;
}
).morphRelations ?? [];
const derivedAtom = atom((get) => {
const record = get(recordStoreFamilyStateV2.atomFamily(recordId));
const morphValuesWithObjectName = morphRelations.map((morphRelation) => {
const computedFieldName = computeMorphRelationFieldName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type as RelationType,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
});
return {
objectNameSingular: morphRelation.targetObjectMetadata.nameSingular,
value: record?.[computedFieldName],
};
});
const relationType = morphRelations[0]?.type;
if (relationType === RelationType.ONE_TO_MANY) {
return morphValuesWithObjectName.map((morphValue) => ({
...morphValue,
value: morphValue.value ? morphValue.value : [],
})) as {
objectNameSingular: string;
value: ObjectRecord[];
}[];
}
if (relationType === RelationType.MANY_TO_ONE) {
const morphValueFiltered = morphValuesWithObjectName.filter(
(morphValue) => isDefined(morphValue.value),
);
return morphValueFiltered.length > 0
? (morphValueFiltered[0] as {
objectNameSingular: string;
value: ObjectRecord;
})
: null;
}
return null;
});
derivedAtom.debugLabel = `recordMorphFieldValue__${cacheKey}`;
morphAtomCache.set(cacheKey, derivedAtom);
return derivedAtom;
};
export const recordStoreFieldValueSelectorV2 = ({
recordId,
fieldName,
fieldDefinition,
}: {
recordId: string;
fieldName: string;
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>;
}): Atom<unknown> => {
if (isFieldMorphRelation(fieldDefinition)) {
return getMorphRelationFieldValueAtom(recordId, fieldName, fieldDefinition);
}
return simpleFieldValueSelector.selectorFamily({ recordId, fieldName });
};
@@ -4,6 +4,7 @@ import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell';
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
@@ -16,6 +17,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDefined } from 'twenty-shared/utils';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -90,6 +92,10 @@ export const useSetRecordTableData = ({
};
set(recordStoreFamilyState(record.id), newRecord);
jotaiStore.set(
recordStoreFamilyStateV2.atomFamily(record.id),
newRecord,
);
}
}