Closed mdlavin closed 2 years ago
I'm also looking for a way to subscribe to a recoil value in an atom's effect now. Will let you know if I find a way but also pls share your solution if you'll find any
Since an atom doesn't expose a get
function, I guess the only way would be to catch a get
function elsewhere and store it in a variable outside of Recoil.
Then you'd be able to use it, but it is a bit hacky, and I don't think it's the intended usage.
I'm also not sure the implications it would have on concurrent rendering.
So I found the solution, in my case its not bi-directional but I imagine this could be achieved with the set
without a problem. Interesting thing here is you'd expect that using this selector in multiple places would cause multiple fetches but it doesn't, which is perfectly what I wanted :) gotta love recoil
Also the result is memoed and so changing activeUsernameState
in recoil to already previously used username also doesn't cause refetch, which is again what I wanted.
Here's the example:
export const suggestedFiltersSelector = selector<SuggestedFilters>({
key: 'suggestedFiltersSelector',
get: async ({ get }) => {
try {
const { data } = await apolloClient.query<
IGetSuggestedFiltersQuery,
IGetSuggestedFiltersQueryVariables
>({
query: GetSuggestedFiltersDocument,
variables: { username: get(activeUsernameState) },
});
return data?.getSuggestedFilters.suggestedFilters;
} catch (error) {
Logger.error(error);
}
return {};
},
});
Hope that also helps you :)
@mdlavin - We are exploring the ability for atom effects to look at the state of other atoms, perhaps via providing a mechanism like getSnapshot()
as a callback for the atom effect.
What about looking at the state of the current atom? Is there a way to find the old/current value in onSet
before the new value is committed? Or the initial value in the case that trigger === 'set'
? Speaking of which, what is the purpose of the node
self reference in the effects? It doesn't seem like I can use it for anything.
The onSet()
callback is provided two parameters, the new value and the old value. Though, the API is still in flux and may need to be tweaked to fully support async/error states.
The node
allows you to do things like build up a list or map of atoms which can be used by other async callbacks.
I'm coming from Jotai competitor. Looked at how RecoilJS can solve my problems.
It looks like atom affects
is what Jotai is. Jotai doesn't have selector
entity, only atoms
.
Moreover atoms
can get and set another atoms
and can even get it's own value.
My use case - use atoms as source of truth and update itself as fast as possible, while updating database is considered as side-effect.
export const tagsAtom = atom(
(_) => {
const tagRepository = getTagRepository();
return tagRepository.find();
},
(
get,
set,
{ id, ...newTag }: QueryDeepPartialEntity<Tag> & { id: string }
) => {
const oldTags = get(tagsAtom);
const newTags = oldTags.map((oldTag) => {
if (oldTag.id === id) {
return {
...oldTag,
...newTag
};
} else {
return oldTag;
}
});
// @ts-expect-error
set(tagsAtom, newTags);
const tagRepository = getTagRepository();
tagRepository.update({ id }, newTag);
}
);
how this can be achieved using RecoilJS? I've read all documentation and still can't figure that out.
@drarmstr Any updates on this one? I might be missing something, but I find it close to impossible to update another atom value when some atom value changes. For now I used the selector similar to this: https://github.com/facebookexperimental/Recoil/issues/707#issuecomment-721050102, but is there any easier way to subscribe to a specific atom changes and update other atoms?
I recommend using family function to get another atom's value in effect.
In component.
const a = useRecoilValue(aState)
const b = useRecoilValue(bState({ a }))
Recoil definition.
const bState = atomFamily({
key: 'bState',
default: undefined,
effects_UNSTABLE: ({ a }) => [
function loadSubscriptions ({ setSelf }) {
const result = getSomeValueFromA(a)
setSelf(result)
}
]
})
Ability to grab other atoms' values inside the effect functions would be very helpful. We need to rehydrate the local state from a remote Firebase collection based on a number of query parameters, already part of various atoms. It wouldn't make much sense to create an atom family when a simple get parameter to the effect functions would suffice. Am I thinking this incorrectly?
also i think it's a good feature if it possible that in effects_UNSTABLE.onset, update other atom. i want some atom's effect propagation to other atom's effect.
Proposed solution in #1205 and #1210
@drarmstr Any idea when this feature will go live for adding getLoadable() and getPromise() in atom effects? This will be quite useful in a problem I am currently facing.
They have already landed in the main branch and so should be available with the nightly build branch.
I believe there is a breaking issue, I am getting cleanup is not a function
when unmounting a component accessing an atom using getPromise
in one of the effects_UNSTABLE.
effects_UNSTABLE: [
async ({getPromise, onSet}) => {
const sdk = await getPromise(SDKState);
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
onSet((newId) => {
sdk.post("/api/some-api-call", {
data: {
key: "value"
},
cancelToken: source.token
});
});
return () => {
source.cancel("Operation canceled due to cleanup.");
};
}
]
Is there something I am doing wrong?
So I figured out the issue above was due to having a async
function in the effects array.
Are there any plans to implement a function to update other atoms inside onSet?
My case is something like this: I would like to reset an atom when another one changes, i.e. I have an atom for the current categoryId. I would like to reset the projects atom when categoryId changes and I would like to be able to do it without an effect in react somewhere, otherwise I am having an implicit dependency on that react hook. I could use the effect of the categoryId but then that atom will have to know of all the atoms that depend on it, which is kinda the inverse of what I am looking for.
So, any news on this?
Closing issue as this feature is now released with 0.5
.
For the pattern of resetting another atom when a category changes you may wish to consider using an atom family pattern as a "scoped atom" -- Use the results of the category as the parameter for the family. See this example
I think there are valid use cases for calling get
inside of an atom effect. Example:
To work around this limitation, one can create a selector and which performs const arg = get(atomA)
and then passes this value to the atom which has the effect get(atomFamilyWithEffect(arg))
but this is problematic because this will create new atoms every time the value of arg
changes, which will cause a memory leak if arg
is highly dynamic (such as based on user input). Perhaps this problem can be solved by providing a way to indicate that a given atom within an atom family is unused and can be safely GC'd, but https://github.com/facebookexperimental/Recoil/issues/366 suggests that this problem has not been solved.
I've been looking into atom effects and they look really helpful. In the example, it shows a state synchronization example effect like this:
That looks perfect, if
myRemoteStorage
is a static, but how would you structure that same effect whenmyRemoteStorage
is coming from a Recoil value? If the datastore is also tracked in a Recoil atom, then I can't figure out a good pattern for using it in an effect. Was there an approach in mind to handle that pattern?