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:
+10
@@ -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));
|
||||
};
|
||||
+13
@@ -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));
|
||||
};
|
||||
+13
@@ -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);
|
||||
};
|
||||
+10
@@ -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));
|
||||
};
|
||||
+9
@@ -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>;
|
||||
};
|
||||
+26
@@ -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
|
||||
>;
|
||||
};
|
||||
+13
@@ -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
|
||||
>;
|
||||
};
|
||||
+11
@@ -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),
|
||||
);
|
||||
};
|
||||
+45
@@ -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,
|
||||
};
|
||||
};
|
||||
+41
@@ -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,
|
||||
};
|
||||
};
|
||||
+86
@@ -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,
|
||||
};
|
||||
};
|
||||
+52
@@ -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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user