Open kevinschaich opened 3 months ago
Hmm, something like this maybe:
const dynamicFocusAtom =
<T extends object>(baseAtom: PrimitiveAtom<T>) =>
<Key extends keyof T>(key: Key): PrimitiveAtom<T[Key]> =>
focusAtom(baseAtom, optic => optic.prop(key))
const baseAtom = atom({ a: 5, b: 'string' } as const) // PrimitiveAtom<{a: number}>
const atomDeriver = dynamicFocusAtom(baseAtom)
const focusA = atomDeriver('a') // PrimitiveAtom<5>
const focusB = atomDeriver('b') // PrimitiveAtom<'string'>
Does that work?
I wonder if optics-ts
support such usage.
From Jotai's perspective, dynamicPropAtom
should be possible and fairly easy without focusAtom
.
https://github.com/jotaijs/jotai-optics/issues/15#issuecomment-2057517757 If that works, it works.
Hey @dai-shi I actually have the same use case. I'm not sure if https://github.com/jotaijs/jotai-optics/issues/15#issuecomment-2057517757 addresses it, because I'd like to pass in a prop when calling the setter, not when declaring the atom or wrapping it in useAtom.
Specifically, I'd like to call my focus atom setter from inside an Ably callback. My atom is an object of dynamic keys, for example
const atom = {
foo: {
a: {
1: 1,
2: 2,
},
b: {
1: 1,
2: 2,
},
},
bar: {
a: {
1: 1,
2: 2,
},
b: {
1: 1,
2: 2,
},
},
}
and I'd like to be able to update specific fields using a path I'm creating from inside the callback because I'm consuming ably messages that contain metadata from which I can construct my path.
const focus = useCallback((optic: OpticFor<SuperTraits>) => optic.path(path), [path])
const [value, setValue] = useAtom(focusAtom(atom, focus))
const { connectionError, channelError } = useChannel(ablyChannelId, (message: Ably.Types.Message) => {
const path = getPathFromMessage(message)
setValue({ path, updatedValue: Math.random() })
I can't declare const [value, setValue] = useAtom(focusAtom(atom, focus))
inside my callback because that would break the rule of hooks. Is there a way to do this without focusAtom as you alluded to?
@seanyboy49 I'm not sure if I follow 100%, but it sounds like you can use a write-only atom or useAtomCallback.
const setValueWithPath = useSetAtom(useMemo(() => atom(null, (get, set, { path, updatedValue }) => {
const a = focusAtom(atom, (optic) => optic.path(path))
set(a, updatedValue)
}), []))
const { connectionError, channelError } = useChannel(ablyChannelId, (message: Ably.Types.Message) => {
const path = getPathFromMessage(message)
setValueWithPath({ path, updatedValue: Math.random() })
@dai-shi thanks for the prompt response! Yes, that does the trick!
I was also able to get it working using optics-ts
in a derived atom
const largeAtom = atom({})
const useJotaiDerived = (path?: string) => {
const read = useCallback(
(get: Getter) => {
const val = get(largeAtom)
if (!path) return val
const optic = O.optic().path(path)
return O.get(optic)(val)
},
[path],
)
const write = useCallback((get: Getter, set: Setter, newValue: { path: string; value: string }) => {
set(largeAtom, (prev) => {
const { path, value } = newValue
const optic = O.optic().path(path)
const updated = O.set(optic)(value)(prev)
return updated
})
}, [])
const derivedTraitInstance = useAtom(useMemo(() => atom(read, write), [read, write]))
return derivedTraitInstance
}
Is it possible to create a generic optic or selector for an object, similar to how
splitAtom
works for arrays?For both
selectAtom
andfocusAtom
, the examples on the website work nicely if you know the property you want in advance, but sometimes you do not.Is it possible for us to add another primitive that does something like the following?
This would allow passing more narrowly scoped atoms down to children, but I'm not sure if we could expect any reasonable performance gains if we don't know the accessor in advance. Would be curious to hear about expected perf gains for
splitAtom
and maybe we can infer from that.