Add atomFamily.getKeys
method in order to simplify family keys management
#2056
Replies: 4 comments 11 replies
-
Well, it's technically possible. Probably call it I have a few comments about my hesitation:
That said, adding If you have an idea to improve |
Beta Was this translation helpful? Give feedback.
-
@dai-shi my suggestion for implementation changes of
import { Atom } from 'jotai';
export type RemoveFamilyParamCallback<Key> = (key: Key, abortAfter: VoidFunction) => boolean;
export type RemoveFamilyParam<Param, Key> = (removeParam: RemoveFamilyParamCallback<Key> | Param) => void;
export type ParamSerializer<Param, Key extends string | number | boolean> = (param: Param) => Key;
export interface TestAtomFamily<Param, Key, AtomType> {
(param: Param): AtomType;
remove: RemoveFamilyParam<Param, Key>;
}
const isRemoveFamilyParamCallback = <Key>(
possiblyCallback: unknown
): possiblyCallback is RemoveFamilyParamCallback<Key> => typeof possiblyCallback === 'function';
export function testAtomFamily<Param, Key extends string | number | boolean, AtomType extends Atom<unknown>>(
initializeAtom: (param: Param) => AtomType,
...[paramSerializer]: Param extends Key
? [paramSerializer?: ParamSerializer<Param, Key>]
: [paramSerializer: ParamSerializer<Param, Key>]
): TestAtomFamily<Param, Key, AtomType> {
const getKey = (param: Param) => (paramSerializer ? paramSerializer(param) : (param as unknown as Key));
const atoms: Map<Key, AtomType> = new Map();
const getAtom = (param: Param) => {
const key = getKey(param);
const item = atoms.get(key);
if (item) {
return item;
}
const newItem = initializeAtom(param);
atoms.set(key, newItem);
return newItem;
};
const removeFamilyParam: RemoveFamilyParam<Param, Key> = removeParam => {
if (isRemoveFamilyParamCallback(removeParam)) {
let shouldAbortAfter = false;
const abortAfter = () => {
shouldAbortAfter = true;
};
for (const key of atoms.keys()) {
if (removeParam(key, abortAfter)) {
atoms.delete(key);
}
if (shouldAbortAfter) {
break;
}
}
} else {
atoms.delete(getKey(removeParam as Param));
}
};
getAtom.remove = removeFamilyParam;
return getAtom;
} Theres stackblitz playground: Note! Types management should be improved. |
Beta Was this translation helpful? Give feedback.
-
My use case for this is essentially the one at in the CodeSandbox example. Essentially, I'm hoping to avoid having to keep track of a separate atom of the items, which the example does via: const todosAtom = atom<string[]>([]); and iterates over like: return todos.filter((id) => !get(todoAtomFamily({ id })).completed); In more complex scenarios, keeping An an alternative approach I'm considering is putting the whole map in an Atom. To iterate over it, just iterate over the value. To treat individual elements as atoms, use an optic. Probably what I really want is |
Beta Was this translation helpful? Give feedback.
-
#2679 A PR by @dmaskasky |
Beta Was this translation helpful? Give feedback.
-
Problem statement.
atomFamily
usesMap
underneath. It is not possible to directly access givenMap
, while also, theres no method onatomFamily
which would allow to get all keys fromMap
.This leads to situation where in order to avoid memory-leaks on "infinite"
atomFamily
(family with unknown number of family members), we have to manually track keys which have been set inMap
.Suggested change
My suggestion is to simply add new method to
atomFamily
API:jotai/src/vanilla/utils/atomFamily.ts
Line 22 in 6a16612
Example approach with current API:
Example approach with new API:
Why can't I use atomFamily.setShouldRemove?
atomFamily.setShouldRemove
executes only when we want to access given key, and it has no ability to read other atoms via getter.If we have entry in
atomFamily
, which is no longer possible to access by UI (it is outside of timeline boundaries), theres no scenario wheresetShouldRemove
could be called.Beta Was this translation helpful? Give feedback.
All reactions