-
-
Notifications
You must be signed in to change notification settings - Fork 581
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
[Tips] Atoms can be created on demand #5
Comments
Hi @dai-shi, thanks for releasing this library! 🙏 For example, when you remove a todo, the garbage collector will kick in? |
Hi @asfktz !
The atom value is removed from the Provider when no components (precisely The atom config is just an object, so it's garbage collected by JS when there' no reference to it.
So, yes. |
Hmm..! I get it now |
Here's the basic working ToDo example: https://codesandbox.io/s/jotai-demo-ijyxm |
Hi! It feels a bit strange to me adding an atom instead of a simple object when you add a todo so I was asking myself if we could use an approach similar to the one used in this recoil video. The idea is to use derived atoms (or selectors in recoil) with a memoized function. Something like this: const todoWithId = momoize(id => atom(
get => get(todosAtom).find(todo => todo.id == id),
(get, set, newData) => {
set(todosAtom, get(todosAtom).map(todo => todo.id == id ? newData : todo)
}
)
const TodoItem: React.FC<{
todoAtom: PrimitiveAtom<Todo>;
remove: () => void;
}> = ({ id,_ remove }) => {
const [item, setItem] = useAtom(todoWithId(id);
const toggleCompleted = () => {
setItem({ ...item, completed: !item.completed });
};
return (
<li>
...
</li>
);
}; Does this approach make sense in the Jotai paradigm? Thank you. |
Yeah, I see what you mean. You might get crazy without TS types. Your approach also seems valid. So, you have a big todosAtom atom, and each todoAtom is a derived atom out of it. (wait, how do you add a new atom to todosAtom?) In this case, you'd need to keep ids somehow, so it would be just easier to use |
My goal is to use only "simple" array/objects values for atoms instead of objects created by an atom function. With this assumption, todosAtom's value is an array of todos (with, id, title and completed) and the derived atoms selects the element of the array that they want. The code will look like this: const filterAtom = atom("all");
const todosAtom = atom([]);
const filteredAtom = atom((get) => {
const filter = get(filterAtom);
const todos = get(todosAtom);
if (filter === "all") return todos;
else if (filter === "completed")
return todos.filter((todo) => todo.completed);
else return todos.filter((todo) => !todo.completed);
});
const todoWithId = memoize((id) =>
atom(
(get) => get(todosAtom).find((todo) => todo.id === id),
(get, set, newData) => {
set(
todosAtom,
get(todosAtom).map((todo) => (todo.id === id ? newData : todo))
);
}
)
);
const TodoList = () => {
const [, setTodos] = useAtom(todosAtom);
const remove = (todo) =>
setTodos((prev) => prev.filter((item) => item !== todo));
const add = (e) => {
e.preventDefault();
const title = e.currentTarget.inputTitle.value;
e.currentTarget.inputTitle.value = "";
setTodos((prev) => [...prev, { id: nanoid(), title, completed: false }]);
};
return (
<form onSubmit={add}>
<Filter />
<input name="inputTitle" placeholder="Type ..." />
<Filtered remove={remove} />
</form>
);
}; I made a fork of your todo demo here. I'm thinking about this idea because it seems more straightforward for me but it does not fit well with this atom paradigm, does it? |
Yes, it totally does. It's just another practice. Create a big atom (well not that big) and derive smaller pieces. I mean proposing such a new practice is always welcomed. I just wanted to point out there could be another practice that fulfill your goal. (Hm, but now I'm not sure how to pass initial title with atomFamily.) |
Yeah, I took a look to atomFamily and it looks interesting. The problem that I detected with the solution I proposed is that I need to update todosAtom's value in order to update one todo, so it rerenders all components that use this atom, directly or indirectly 😓. So, it breaks the idea behind recoil (as far as I understood the message of the video I linked above). |
You can fix it with
Finally made the version with atomFamily (I needed to use |
Thanks for the example. One question: if you want to load the todos from an API or a localStorage, where will you do this load with atomFamily? |
That's actually a good question. Basically, it doesn't change anything with atomFamily, I suppose, but we'd still need to come up with better ideas for save/load (serialize/deserialize) features. #4 |
Well, it needs to take the |
@MeloJR Oh, I see. You are right. I missed it. const todoAtomFamily = atomFamily(
(param) => ({ title: param.title, completed: param.completed ?? false }),
null,
(a, b) => a.id === b.id
) Something like this 👆 . It's a bit annoying that this basic atoms have to take care of (de)serialization. I'd like to separate it somehow. const todoAtomFamily = atomFamily(
(param) => ({ title: param.title, completed: false }),
null,
(a, b) => a.id === b.id
)
const serializeTodo = ...
const deserializeTodo = ...
const serializableTodoAtomFamily = atomFamily(
(param) => (get) => serialize(get(todoAtomFamily(param))),
(params) => (_get, set, arg) => set(todoAtomFamily(param), deserialize(arg)),
(a, b) => a.id === b.id
) You know, we are still challenging about (de)serialization. Ideas are welcome. Eventually, a good pattern will turn into a helper function. |
I made the todos example with atomFamily to support persistence with localStorage. Please check it out and let us know if you find any issues. |
Great! I have checked it quickly and it looks very interesting. I will code some concepts that I wanted to test following your example. If I find any issue I will post it here. Thanks! |
Atoms can be created at anytime, like event callbacks and useEffect, or in promises.
Here's a simple Todo example.
The text was updated successfully, but these errors were encountered: