You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
You have a lot of derived atoms that are selecting from large object atoms, and want to avoid the small ones publishing changes if their derived values don't change. Publishing changes every time can be undesirable for several reasons:
Performance can be worse if your derived atoms are all doing a non-trivial amount of work in their read functions
Any components which mount one of the derived atoms will re-render, even if their specific value hasn't changed
Async atoms re-suspend, causing your suspense boundaries to also re-suspend and make that section of your page "flicker" even if the content doesn't change
With vanilla jotai and no utils, you might have something like this
constbigAtom=atom({// this is demonstrative; imagine this is a very big object with many nested propertiesa: 1,b: 2,c: 3,})// let's say this is mounted in a component via useAtomValueconstsmallAtom=atom((get)=>get(bigAtom).a)// smallAtom ideally should not re-render here, but it does because bigAtom changesstore.set(bigAtom,{ ...store.get(bigAtom),b: 2})
This is probably the right behavior for most people, but it's not the right behavior for everyone.
Existing discussions (non-exhaustive):
Many people have asked about this. If we have a snippet somewhere I'm happy to use that instead of the proposed code below.
derivedAtomWithCompare: derived atom that keeps track of the previous value and deeply compares it to the next value, do not republish changes to subscribers if they are the same
if on read the derived value is different, updates the previous value and return the new value
if they are the same, don't publish changes at all (keep the existing value in all the mounted locations)
provide an initial static value and a read function which accepts a Getter
can have an async version that works with promises and a non-async version
Differences from existing utils:
It's similar to selectAtom, but the goal here is to provide a generic read-only atom that can do anything a normal derived atom can (needs access to a Getter)
It's similar to atomWithCache, but you want to check if the returned value is deeply equal to the previous returned value, rather than the dependencies of the get function. The dependency atoms will most likely change every time because they keep a lot of other things unrelated to this atom value.
It's similar to atomWithCompare, but your read function needs to be dynamic and read from other atoms, rather than having the returned atom be a PrimitiveAtom that gets set directly.
Might be similar to bunshi (formerly jotai-molecules)? I'm not entirely sure.
Current Progress:
It's not working 100% but I think I'm pretty close.
import{Atom,atom,Getter}from'jotai'import{isEqual}from'lodash'exportconstderivedAtomWithCompareAsync=<T>(read: (get: Getter)=>Promise<T>,initialValue: T,areEqual?: (prev: any,next: any)=>boolean,): Atom<Promise<T>>=>{letpreviousValue=initialValueareEqual=areEqual??((prev,next)=>isEqual(prev,next))constderivedAtom=atom(async(get)=>{constnext=awaitread(get)constarePreviousAndNextEqual=areEqual(previousValue,next)if(!arePreviousAndNextEqual){previousValue=nextreturnnext}// this does not quite work yet, it still publishes changes to all subscribers, but I think I'm closereturnpreviousValue})returnderivedAtom}
Nice RFC. I agree that compare functionality could be brought to derived atoms to make ergonomics better.
I would love to see this added as a utility to jotai-history. Feel free to submit a PR.
A few thoughts below:
previousValue should be in an atom so that the atom doesn't share states between stores.
constprevAtom=atom(()=>{previousValue})
You can use withHistory from jotai-history to assist with prev/curr values.
I think it would be great if it could be something like withCompare and accepted both derived and primitive atoms. Then we could deprecate atomWithCompare.
Use case:
You have a lot of derived atoms that are selecting from large object atoms, and want to avoid the small ones publishing changes if their derived values don't change. Publishing changes every time can be undesirable for several reasons:
With vanilla jotai and no utils, you might have something like this
This is probably the right behavior for most people, but it's not the right behavior for everyone.
Existing discussions (non-exhaustive):
Many people have asked about this. If we have a snippet somewhere I'm happy to use that instead of the proposed code below.
#1158, #783, #324, #1175, #26
Proposal:
derivedAtomWithCompare
: derived atom that keeps track of the previous value and deeply compares it to the next value, do not republish changes to subscribers if they are the sameread
function which accepts aGetter
Differences from existing utils:
selectAtom
, but the goal here is to provide a generic read-only atom that can do anything a normal derived atom can (needs access to aGetter
)atomWithCache
, but you want to check if the returned value is deeply equal to the previous returned value, rather than the dependencies of theget
function. The dependency atoms will most likely change every time because they keep a lot of other things unrelated to this atom value.atomWithCompare
, but your read function needs to be dynamic and read from other atoms, rather than having the returned atom be aPrimitiveAtom
that gets set directly.bunshi
(formerlyjotai-molecules
)? I'm not entirely sure.Current Progress:
It's not working 100% but I think I'm pretty close.
Assignee:
@kevinschaich
The text was updated successfully, but these errors were encountered: