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
@@ -1,8 +1,8 @@
import { type FieldDateMetadataSettings } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { UserContext } from '@/users/contexts/UserContext';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import { formatDateString } from '~/utils/string/formatDateString';
import { EllipsisDisplay } from './EllipsisDisplay';
@@ -12,7 +12,7 @@ type DateDisplayProps = {
};
export const DateDisplay = ({ value, dateFieldSettings }: DateDisplayProps) => {
const { dateFormat } = useContext(UserContext);
const dateLocale = useRecoilValue(dateLocaleState);
const dateLocale = useRecoilValueV2(dateLocaleStateV2);
const formattedDate = formatDateString({
value,
@@ -1,12 +1,12 @@
import { type FieldDateMetadataSettings } from '@/object-record/record-field/ui/types/FieldMetadata';
import { TimeZoneAbbreviation } from '@/ui/input/components/internal/date/components/TimeZoneAbbreviation';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { UserContext } from '@/users/contexts/UserContext';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { Temporal } from 'temporal-polyfill';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
import { formatDateTimeString } from '~/utils/string/formatDateTimeString';
import { EllipsisDisplay } from './EllipsisDisplay';
@@ -24,7 +24,7 @@ export const DateTimeDisplay = ({
dateFieldSettings,
}: DateTimeDisplayProps) => {
const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
const dateLocale = useRecoilValue(dateLocaleState);
const dateLocale = useRecoilValueV2(dateLocaleStateV2);
const formattedDate = formatDateTimeString({
value,
@@ -1,11 +1,12 @@
import { downloadFile } from '@/activities/files/utils/downloadFile';
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
import { isAttachmentPreviewEnabledStateV2 } from '@/client-config/states/isAttachmentPreviewEnabledStateV2';
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { FileChip } from '@/ui/field/display/components/FileChip';
import { UploadFileChip } from '@/ui/field/display/components/UploadFileChip';
import { filePreviewState } from '@/ui/field/display/states/filePreviewState';
import { filePreviewStateV2 } from '@/ui/field/display/states/filePreviewStateV2';
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { isDefined } from 'twenty-shared/utils';
type FilesDisplayProps = {
@@ -21,9 +22,9 @@ export const FilesDisplay = ({
isUploadWindowOpen = false,
isFileUploading = false,
}: FilesDisplayProps) => {
const setFilePreview = useSetRecoilState(filePreviewState);
const isAttachmentPreviewEnabled = useRecoilValue(
isAttachmentPreviewEnabledState,
const setFilePreview = useSetRecoilStateV2(filePreviewStateV2);
const isAttachmentPreviewEnabled = useRecoilValueV2(
isAttachmentPreviewEnabledStateV2,
);
const handlePreview = (file: FieldFilesValue) => {
@@ -1,13 +1,13 @@
import { downloadFile } from '@/activities/files/utils/downloadFile';
import { filePreviewState } from '@/ui/field/display/states/filePreviewState';
import { filePreviewStateV2 } from '@/ui/field/display/states/filePreviewStateV2';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { lazy, Suspense, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconDownload, IconX } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
@@ -72,7 +72,7 @@ const StyledLoadingText = styled.div`
export const GlobalFilePreviewModal = (): JSX.Element | null => {
const { t } = useLingui();
const [filePreview, setFilePreview] = useRecoilState(filePreviewState);
const [filePreview, setFilePreview] = useRecoilStateV2(filePreviewStateV2);
const { openModal, closeModal } = useModal();
useEffect(() => {
@@ -0,0 +1,7 @@
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const filePreviewStateV2 = createStateV2<FieldFilesValue | null>({
key: 'filePreviewStateV2',
defaultValue: null,
});
@@ -0,0 +1,10 @@
import { useAtomValue } from 'jotai';
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
export const useFamilyRecoilValueV2 = <ValueType, FamilyKey>(
familyState: FamilyStateV2<ValueType, FamilyKey>,
familyKey: FamilyKey,
): ValueType => {
return useAtomValue(familyState.atomFamily(familyKey));
};
@@ -0,0 +1,13 @@
import { useAtom } from 'jotai';
import { type WritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/types/WritableFamilySelectorV2';
export const useFamilySelectorStateV2 = <ValueType, FamilyKey>(
familySelector: WritableFamilySelectorV2<ValueType, FamilyKey>,
familyKey: FamilyKey,
): [
ValueType,
(value: ValueType | ((prev: ValueType) => ValueType)) => void,
] => {
return useAtom(familySelector.selectorFamily(familyKey));
};
@@ -0,0 +1,13 @@
import { useAtomValue } from 'jotai';
import { type FamilySelectorV2 } from '@/ui/utilities/state/jotai/types/FamilySelectorV2';
import { type WritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/types/WritableFamilySelectorV2';
export const useFamilySelectorValueV2 = <ValueType, FamilyKey>(
familySelector:
| FamilySelectorV2<ValueType, FamilyKey>
| WritableFamilySelectorV2<ValueType, FamilyKey>,
familyKey: FamilyKey,
): ValueType => {
return useAtomValue(familySelector.selectorFamily(familyKey));
};
@@ -0,0 +1,13 @@
import { useAtom } from 'jotai';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
import { type WritableSelectorV2 } from '@/ui/utilities/state/jotai/types/WritableSelectorV2';
export const useRecoilStateV2 = <ValueType>(
state: StateV2<ValueType> | WritableSelectorV2<ValueType>,
): [
ValueType,
(value: ValueType | ((prev: ValueType) => ValueType)) => void,
] => {
return useAtom(state.atom);
};
@@ -0,0 +1,14 @@
import { useAtomValue } from 'jotai';
import { type SelectorV2 } from '@/ui/utilities/state/jotai/types/SelectorV2';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
import { type WritableSelectorV2 } from '@/ui/utilities/state/jotai/types/WritableSelectorV2';
export const useRecoilValueV2 = <ValueType>(
state:
| StateV2<ValueType>
| SelectorV2<ValueType>
| WritableSelectorV2<ValueType>,
): ValueType => {
return useAtomValue(state.atom);
};
@@ -0,0 +1,10 @@
import { useSetAtom } from 'jotai';
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
export const useSetFamilyRecoilStateV2 = <ValueType, FamilyKey>(
familyState: FamilyStateV2<ValueType, FamilyKey>,
familyKey: FamilyKey,
): ((value: ValueType | ((prev: ValueType) => ValueType)) => void) => {
return useSetAtom(familyState.atomFamily(familyKey));
};
@@ -0,0 +1,9 @@
import { useSetAtom } from 'jotai';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
export const useSetRecoilStateV2 = <ValueType>(
state: StateV2<ValueType>,
): ((value: ValueType | ((prev: ValueType) => ValueType)) => void) => {
return useSetAtom(state.atom);
};
@@ -0,0 +1,3 @@
import { createStore } from 'jotai';
export const jotaiStore = createStore();
@@ -0,0 +1,7 @@
import { type Atom } from 'jotai';
export type FamilySelectorV2<ValueType, FamilyKey> = {
type: 'FamilySelectorV2';
key: string;
selectorFamily: (key: FamilyKey) => Atom<ValueType>;
};
@@ -0,0 +1,13 @@
import { type WritableAtom } from 'jotai';
type JotaiWritableAtom<ValueType> = WritableAtom<
ValueType,
[ValueType | ((prev: ValueType) => ValueType)],
void
>;
export type FamilyStateV2<ValueType, FamilyKey> = {
type: 'FamilyStateV2';
key: string;
atomFamily: (key: FamilyKey) => JotaiWritableAtom<ValueType>;
};
@@ -0,0 +1,26 @@
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
export type SelectorGetterV2 = {
get: {
<ValueType>(state: StateV2<ValueType>): ValueType;
<ValueType, FamilyKey>(
familyState: FamilyStateV2<ValueType, FamilyKey>,
familyKey: FamilyKey,
): ValueType;
};
};
export type SelectorSetterV2 = SelectorGetterV2 & {
set: {
<ValueType>(
state: StateV2<ValueType>,
value: ValueType | ((prev: ValueType) => ValueType),
): void;
<ValueType, FamilyKey>(
familyState: FamilyStateV2<ValueType, FamilyKey>,
familyKey: FamilyKey,
value: ValueType | ((prev: ValueType) => ValueType),
): void;
};
};
@@ -0,0 +1,7 @@
import { type Atom } from 'jotai';
export type SelectorV2<ValueType> = {
type: 'SelectorV2';
key: string;
atom: Atom<ValueType>;
};
@@ -0,0 +1,11 @@
import { type WritableAtom } from 'jotai';
export type StateV2<ValueType> = {
type: 'StateV2';
key: string;
atom: WritableAtom<
ValueType,
[ValueType | ((prev: ValueType) => ValueType)],
void
>;
};
@@ -0,0 +1,13 @@
import { type WritableAtom } from 'jotai';
export type WritableFamilySelectorV2<ValueType, FamilyKey> = {
type: 'WritableFamilySelectorV2';
key: string;
selectorFamily: (
key: FamilyKey,
) => WritableAtom<
ValueType,
[ValueType | ((prev: ValueType) => ValueType)],
void
>;
};
@@ -0,0 +1,11 @@
import { type WritableAtom } from 'jotai';
export type WritableSelectorV2<ValueType> = {
type: 'WritableSelectorV2';
key: string;
atom: WritableAtom<
ValueType,
[ValueType | ((prev: ValueType) => ValueType)],
void
>;
};
@@ -0,0 +1,21 @@
import { type Getter } from 'jotai';
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
export const buildGetHelper =
(jotaiGet: Getter) =>
<ValueType, FamilyKey = never>(
stateOrFamily: StateV2<ValueType> | FamilyStateV2<ValueType, FamilyKey>,
familyKey?: FamilyKey,
): ValueType => {
if (stateOrFamily.type === 'FamilyStateV2') {
return jotaiGet(
(stateOrFamily as FamilyStateV2<ValueType, FamilyKey>).atomFamily(
familyKey as FamilyKey,
),
);
}
return jotaiGet((stateOrFamily as StateV2<ValueType>).atom);
};
@@ -0,0 +1,27 @@
import { type Setter } from 'jotai';
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
export const buildSetHelper =
(jotaiSet: Setter) =>
<ValueType, FamilyKey = never>(
stateOrFamily: StateV2<ValueType> | FamilyStateV2<ValueType, FamilyKey>,
valueOrFamilyKey: ValueType | ((prev: ValueType) => ValueType) | FamilyKey,
familyValue?: ValueType | ((prev: ValueType) => ValueType),
): void => {
if (stateOrFamily.type === 'FamilyStateV2') {
jotaiSet(
(stateOrFamily as FamilyStateV2<ValueType, FamilyKey>).atomFamily(
valueOrFamilyKey as FamilyKey,
),
familyValue as ValueType | ((prev: ValueType) => ValueType),
);
return;
}
jotaiSet(
(stateOrFamily as StateV2<ValueType>).atom,
valueOrFamilyKey as ValueType | ((prev: ValueType) => ValueType),
);
};
@@ -0,0 +1,45 @@
import { atom, type Atom } from 'jotai';
import { type FamilySelectorV2 } from '@/ui/utilities/state/jotai/types/FamilySelectorV2';
import { type SelectorGetterV2 } from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2';
import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper';
export const createFamilySelectorV2 = <ValueType, FamilyKey>({
key,
get,
}: {
key: string;
get: (familyKey: FamilyKey) => (callbacks: SelectorGetterV2) => ValueType;
}): FamilySelectorV2<ValueType, FamilyKey> => {
const atomCache = new Map<string, Atom<ValueType>>();
const selectorFamily = (familyKey: FamilyKey): Atom<ValueType> => {
const cacheKey =
typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey);
const existing = atomCache.get(cacheKey);
if (existing !== undefined) {
return existing;
}
const getForKey = get(familyKey);
const derivedAtom = atom((jotaiGet) => {
const getHelper = buildGetHelper(jotaiGet);
return getForKey({ get: getHelper });
});
derivedAtom.debugLabel = `${key}__${cacheKey}`;
atomCache.set(cacheKey, derivedAtom);
return derivedAtom;
};
return {
type: 'FamilySelectorV2',
key,
selectorFamily,
};
};
@@ -0,0 +1,41 @@
import { atom } from 'jotai';
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
export const createFamilyStateV2 = <ValueType, FamilyKey>({
key,
defaultValue,
}: {
key: string;
defaultValue: ValueType;
}): FamilyStateV2<ValueType, FamilyKey> => {
const atomCache = new Map<
string,
ReturnType<FamilyStateV2<ValueType, FamilyKey>['atomFamily']>
>();
const familyFunction = (
familyKey: FamilyKey,
): ReturnType<FamilyStateV2<ValueType, FamilyKey>['atomFamily']> => {
const cacheKey =
typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey);
const existing = atomCache.get(cacheKey);
if (existing !== undefined) {
return existing;
}
const baseAtom = atom(defaultValue);
baseAtom.debugLabel = `${key}__${cacheKey}`;
atomCache.set(cacheKey, baseAtom);
return baseAtom;
};
return {
type: 'FamilyStateV2',
key,
atomFamily: familyFunction,
};
};
@@ -0,0 +1,27 @@
import { atom } from 'jotai';
import { type SelectorGetterV2 } from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2';
import { type SelectorV2 } from '@/ui/utilities/state/jotai/types/SelectorV2';
import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper';
export const createSelectorV2 = <ValueType>({
key,
get,
}: {
key: string;
get: (callbacks: SelectorGetterV2) => ValueType;
}): SelectorV2<ValueType> => {
const derivedAtom = atom((jotaiGet) => {
const getHelper = buildGetHelper(jotaiGet);
return get({ get: getHelper });
});
derivedAtom.debugLabel = key;
return {
type: 'SelectorV2',
key,
atom: derivedAtom,
};
};
@@ -0,0 +1,20 @@
import { atom } from 'jotai';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
export const createStateV2 = <ValueType>({
key,
defaultValue,
}: {
key: string;
defaultValue: ValueType;
}): StateV2<ValueType> => {
const baseAtom = atom(defaultValue);
baseAtom.debugLabel = key;
return {
type: 'StateV2',
key,
atom: baseAtom,
};
};
@@ -0,0 +1,86 @@
import { atom, type WritableAtom } from 'jotai';
import {
type SelectorGetterV2,
type SelectorSetterV2,
} from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2';
import { type WritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/types/WritableFamilySelectorV2';
import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper';
import { buildSetHelper } from '@/ui/utilities/state/jotai/utils/buildSetHelper';
export const createWritableFamilySelectorV2 = <ValueType, FamilyKey>({
key,
get,
set,
}: {
key: string;
get: (familyKey: FamilyKey) => (callbacks: SelectorGetterV2) => ValueType;
set: (
familyKey: FamilyKey,
) => (callbacks: SelectorSetterV2, newValue: ValueType) => void;
}): WritableFamilySelectorV2<ValueType, FamilyKey> => {
const atomCache = new Map<
string,
WritableAtom<
ValueType,
[ValueType | ((prev: ValueType) => ValueType)],
void
>
>();
const selectorFamily = (
familyKey: FamilyKey,
): WritableAtom<
ValueType,
[ValueType | ((prev: ValueType) => ValueType)],
void
> => {
const cacheKey =
typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey);
const existing = atomCache.get(cacheKey);
if (existing !== undefined) {
return existing;
}
const getForKey = get(familyKey);
const setForKey = set(familyKey);
const derivedAtom = atom(
(jotaiGet) => {
const getHelper = buildGetHelper(jotaiGet);
return getForKey({ get: getHelper });
},
(
jotaiGet,
jotaiSet,
valueOrUpdater: ValueType | ((prev: ValueType) => ValueType),
) => {
const getHelper = buildGetHelper(jotaiGet);
const setHelper = buildSetHelper(jotaiSet);
const resolvedValue =
typeof valueOrUpdater === 'function'
? (valueOrUpdater as (prev: ValueType) => ValueType)(
getForKey({ get: getHelper }),
)
: valueOrUpdater;
setForKey({ get: getHelper, set: setHelper }, resolvedValue);
},
);
derivedAtom.debugLabel = `${key}__${cacheKey}`;
atomCache.set(cacheKey, derivedAtom);
return derivedAtom;
};
return {
type: 'WritableFamilySelectorV2',
key,
selectorFamily,
};
};
@@ -0,0 +1,52 @@
import { atom } from 'jotai';
import {
type SelectorGetterV2,
type SelectorSetterV2,
} from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2';
import { type WritableSelectorV2 } from '@/ui/utilities/state/jotai/types/WritableSelectorV2';
import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper';
import { buildSetHelper } from '@/ui/utilities/state/jotai/utils/buildSetHelper';
export const createWritableSelectorV2 = <ValueType>({
key,
get,
set,
}: {
key: string;
get: (callbacks: SelectorGetterV2) => ValueType;
set: (callbacks: SelectorSetterV2, newValue: ValueType) => void;
}): WritableSelectorV2<ValueType> => {
const derivedAtom = atom(
(jotaiGet) => {
const getHelper = buildGetHelper(jotaiGet);
return get({ get: getHelper });
},
(
jotaiGet,
jotaiSet,
valueOrUpdater: ValueType | ((prev: ValueType) => ValueType),
) => {
const getHelper = buildGetHelper(jotaiGet);
const setHelper = buildSetHelper(jotaiSet);
const resolvedValue =
typeof valueOrUpdater === 'function'
? (valueOrUpdater as (prev: ValueType) => ValueType)(
get({ get: getHelper }),
)
: valueOrUpdater;
set({ get: getHelper, set: setHelper }, resolvedValue);
},
);
derivedAtom.debugLabel = key;
return {
type: 'WritableSelectorV2',
key,
atom: derivedAtom,
};
};