-
-
Notifications
You must be signed in to change notification settings - Fork 589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to reset the atom to its original default value? #41
Comments
Thanks for opening up this discussion. An instant question in mind is what would be the use case. |
Thank you for the amazing library. In my case I have an atom that keeps the status of a component. It has a default value and the only "one" that knows the default value is the atom himself. Currently I have the workaround of keeping a defaultValue const together with the atom like this: export const answerDefaultValue = 42;
export const answerAtom = atom(answerDefaultValue); When I need to reset the value I do as follows: let [answer, setAnswer] = useAtom(answerAtom);
useEffect(() => {
// [...] imaginary code here
return () => {
setAnswer(answerDefaultValue);
}
}, []); But I think it would be great to be able to reset it. Something like: export const answerAtom = atom(42);
let [answer, setAnswer, resetAnswer] = useAtom(answerAtom);
useEffect(() => {
// [...] imaginary code here
return () => {
resetAnswer();
}
}, []);
// or
let [answer, setAnswer] = useAtom(answerAtom);
let resetAnswer = useResetAtom(answerAtom);
useEffect(() => {
// [...] imaginary code here
return () => {
resetAnswer();
}
}, []); Doc of the Recoil version of this use case: https://recoiljs.org/docs/api-reference/core/useResetRecoilState |
Thanks for the example. There are various ways to implement it. As for the code, in addition to yours, I can propose something like this: import { RESET, resetAtom } from 'jotai/utils'
const resettableAtom = resetAtom(42);
const [value, setValue] = useAtom(resettableAtom)
setValue(RESET) The thing to consider is it's only for primitive atoms. derived atoms don't have the notion of reset/initialValue. |
Maybe it should be more explicit: import { useResetAtom, atomWithReset } from 'jotai/utils'
const resettableAtom = atomWithReset(42)
const resetAtom = useResetAtom(resettableAtom) |
I really liked the second one. Feels more natural. |
@dai-shi How to use I have this atom which represents tags attached to task (todo-list): const taskTagsAtom = atom(
(get) => {
const tags = get(tagsAtom);
return tags;
},
(get, set, { id, isSelected }: { id: string; isSelected: boolean }) => {
const tags = get(taskTagsAtom);
const updatedTags = produce(Array.from(tags), (draft) => {
const tag = draft.find((it) => it.id === id);
if (tag !== undefined) {
tag.isSelected = isSelected;
}
});
// @ts-expect-error
set(taskTagsAtom, updatedTags);
}
); Shown tasks can be filtered by tags. When new task is created it inherits I would expect something like this to work: const [taskTags, updateTag] = useAtom(taskTagsAtom);
const resetTaskTags = useResetAtom(taskTagsAtom); |
It's true because it only has the notion of default value. Now, as you use a writable derived atom, it's your responsibility to write a reset handler. const taskTagsAtom = atom(
(get) => {
const tags = get(tagsAtom);
return tags;
},
(get, set, action: { id: string; isSelected: boolean } | 'reset') => {
const tags = get(taskTagsAtom);
if (action === 'reset') {
set(taskTagsAtom, tags); // is this what you want by reset?
return;
}
const { id, isSelected } = action;
const tags = get(taskTagsAtom);
const updatedTags = produce(Array.from(tags), (draft) => {
const tag = draft.find((it) => it.id === id);
if (tag !== undefined) {
tag.isSelected = isSelected;
}
});
// @ts-expect-error
set(taskTagsAtom, updatedTags);
}
); |
Finally I wanted this code to work. But it doesn't work. 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;
}
});
console.log(`dbTagsAtom: oldTags are ${JSON.stringify(oldTags, null, 2)}`);
console.log(`dbTagsAtom: newTags are ${JSON.stringify(newTags, null, 2)}`);
// @ts-expect-error
set(tagsAtom, newTags);
const tagRepository = getTagRepository();
tagRepository.update({ id }, newTag);
}
);
const taskTagsAtom: WritableAtom<db.Tag[], ActionTaskTags> = atom<
db.Tag[],
ActionTaskTags
>(
(get) => {
const tags = get(tagsAtom);
return tags;
},
(get, set, action) => {
if (action.kind === TaskTagsKind.SELECT) {
console.log(`taskTagsAtom: SELECT WAS CALLED`);
const taskTags = get(taskTagsAtom);
const updatedTags = produce(Array.from(taskTags), (draft) => {
const tag = draft.find((it) => it.id === action.id);
if (tag !== undefined) {
tag.isSelected = action.isSelected;
}
});
console.log(
`taskTagsAtom: updatedTags are ${JSON.stringify(updatedTags, null, 2)}`
);
// @ts-expect-error
set(taskTagsAtom, updatedTags);
} else if (action.kind === TaskTagsKind.RESET) {
console.log(`taskTagsAtom: RESET WAS CALLED`);
// Return original tags
const tags = get(tagsAtom);
console.log(`taskTagsAtom: tags are ${JSON.stringify(tags, null, 2)}`);
// @ts-expect-error
set(taskTagsAtom, tags);
}
}
);
On reset Unfortunately this code does't work. On reset I see this code returns modified tags.
But I don't understand why, I haven't modified |
@likern Basically, you would like to avoid setting self in I can help completing your code, but you would want to understand it first. And, I guess this is our fault with the lack of good documentation. |
Basically, what you struggle with is the persistence pattern. |
As I understand, I'm still investigating the issue, but it looks like it's a bug of immer itself immerjs/immer#626. And But that issue leads to another concern. Since atoms are not immutable it's very easy to modify accidentally data directly by reference (without using To understand / trace where it happened is literally impossible. It can be in any line of code. I'm lucky that I got that bug early. For example in my case this code const updatedTags = produce(Array.from(taskTags), (draft) => {
const tag = draft.find((it) => it.id === action.id);
if (tag !== undefined) {
tag.isSelected = action.isSelected;
}
});
console.log(
`taskTagsAtom: updatedTags are ${JSON.stringify(updatedTags, null, 2)}`
); not only modifies Calling const tags = get(tagsAtom) right after above code returns modified data. State becomes as one big global object, and any changes in local state might inherently modify other components all over the place. |
I misunderstood something in your code. So, the tagsAtom can simply be like this? export const tagsAtom = atom(
() => {
const tagRepository = getTagRepository();
return tagRepository.find();
},
) Then, const tags = get(tagsAtom) should always return a same value, yes. |
An atom config object created by |
@dai-shi Yes, I provided definition of But the problem is that tag.isSelected = action.isSelected inherently modifies |
@dai-shi Even though logically I worked with const taskTags = get(taskTagsAtom); |
Oh, now I get it. My apologies for some comments based on my misunderstanding. So, I guess it's the issue of
Yes, this is mutating an atom value, which is not desired. To be fair, we have the same issue with |
Not sure what you mean. We agree the issue is mutating an atom value (object) accidentally, no? Let's open a new issue for this discussion? (this issue is about "reset") |
My main goal here is to discuss about it if there's no core functionality or util that does it.
The text was updated successfully, but these errors were encountered: