-
-
Notifications
You must be signed in to change notification settings - Fork 622
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
Recursive AST atoms [Question] #783
Comments
Yeah, this is still a field we don't have specific recommendations. Happy to have you on board. First of all, option 1 is good as written in a guide. I'm not very satisfied with it either because it doesn't feel like jotai's way, and technically splitAtom is super performant. (Don't get it wrong. It should work great. My perspective is from the library implementation.) Quoting your questions:
Using nested Providers is a valid pattern, which can be said underrated.
Jotai is absolutely developed on top of Context. Basically, all Context features are available in jotai.
I'm not sure if I understand the goal. So, you want to avoid reading a node that is not in the children?
I would explore atoms-in-atom pattern. |
Oops, you are right. Reading this again, it was a bit misleading.
right, scopes are not designed for this use cases. I hope this should be solved in atom-in-atom pattern with or without nested providers. <Provider key={parentAtom} initialValues={[[parentAtomAtom, parentAtom]]}> On second thought, as your AST already exists outside React, option 1 seems fairly reasonable. |
Option 1 is working, but the boilerplate is a bit cumbersome. Option 2 would be about reducing prop drilling by being able to access your "in scope" node atom as a statically referenceable constant. It's about simplifying and making the node atom tree (do we say molecule?) statically defined instead of created dynamically. This is possible for the global scoped atoms. This is possible for node scoped atoms. But if you need to connect these two molecules, you need hooks to allow recursion. |
Then, I'm pretty sure one good solution is: |
The problem with To be fair, hooks don't support recursion without adding some sort of wrapping component to act as the glue, too. |
I've got an example here: https://codesandbox.io/s/recursive-jotai-pfv5i?file=/src/useScopedAtom.tsx I believe that since the atoms from The basic interface is the
I'll probably want to remove the word "scope" and rename to something like "ContextualAtomCreator" and "useContextualAtom". Is there something performance-wise that might be missing here? Is there an reference equality landmine? Any ideas on how to improve the API surface area? |
Oh, you are absolutely right. I was missing that point. Then, Provider |
Thanks for the example! Naming is not good either. Typing could be improved.
No, it looks good. |
Nice! I'll play around with the API a bit, but I think this does a good job of removing boilerplate, decoupling components and allowing recursion. Hopefully it's also efficient. |
Thanks for your help! |
Good to hear! This is probably the first time to see the real Provider |
I had a similar kind of design decision as @loganvolkers with a complex recursive structure: use atoms in atoms or providers. I tried both and this discussion helped. I find the provider with scopes to more flexible because I can then have the atom in module scope and pass it around freely, unlike an atom that is passed as a prop. It might be nice at some point if an example of complex provider usage with scopes was added to the docs. |
Still one question or problem with this approach is still tripping me up. I have many derived atoms. Now that I have nested scopes, these are not working. So I have |
scopes are not tied to atoms. The real issue is that a derived atom can't access another atom in a different scope, by design (otherwise, it's not "scoping"). Now, I know there are some use cases that we want to access atom values across providers, but not it's possible. If your use case falls into this, maybe the scope is not the right tool for you. If we really need to combine two atom values from different scopes, only the solution is doing in a hook (or a component). |
On a side note, I would like to explore a pattern like atoms-in-atom, that can work for recursive structure, without providers. |
I have returned to the atoms-in-atom structure so I could get rid of the inner provider and just have a single one around the main component. I now have a list of trees, a single atom, and each tree is an atom and each tree node is now an atom. Scoping seemed easier to reason about at first but the end result was more complex--once I embraced passing atoms as props more, it turned out to be more verbose but familiar and React-ish and it is much easier to use derived atoms and callbacks in atoms this way to handle updates at different levels. Btw one way in Svelte I have handled similar challenges is by passing Svelte stores around via context to avoid prop drilling. Can you put pass an atom itself via React.Context? --don't need this currently really just wondering if it is possible. |
I'm interested in how you did the atoms-in-atom structure recursively. Yeah, inner provider can be hard to deal with in this case. It works well if we create an isolated component or a library. Passing an atom itself via React Context is totally fine (as well as via props). |
In my case it is quite simple, the container component is recursive but the data is flattened to a tree of atom nodes, each with a parent id. There is a parent Container component (ul) which is rendered recursively by the Node (li) component. This creates a tree of drag and droppable nodes. There is actually a list of these, so mulitple trees.
|
Solved this with a new library https://github.com/saasquatch/jotai-molecules There are a lot of tests and examples. Thanks for the help @dai-shi in figuring out how to create non-global atoms in some good ways. |
It looks very nice!!
You did it! ❤️ cc: @aulneau may find this interesting. |
At SaaSquatch we build a lot of AST editors libraries that edit a homogeneous tree of AST nodes of arbitrary depth. Jotai seems to be a good fit for a lot of the state management use cases, but I'm looking for some guidance on the best way of dealing with recursion.
Example JSON for an HTML AST
```json { "start": 0, "end": 108, "type": "Fragment", "children": [ { "start": 0, "end": 15, "type": "Element", "name": "!DOCTYPE", "attributes": [ { "start": 10, "end": 14, "type": "Attribute", "name": "html", "value": true } ], "children": [] }, { "start": 15, "end": 16, "type": "Text", "raw": "\n", "data": "\n" }, { "start": 16, "end": 108, "type": "Element", "name": "html", "attributes": [], "children": [ { "start": 22, "end": 24, "type": "Text", "raw": "\n\n", "data": "\n\n" }, { "start": 24, "end": 99, "type": "Element", "name": "body", "attributes": [], "children": [ { "start": 30, "end": 35, "type": "Text", "raw": "\n ", "data": "\n " }, { "start": 35, "end": 60, "type": "Element", "name": "h1", "attributes": [], "children": [ { "start": 39, "end": 55, "type": "Text", "raw": "My First Heading", "data": "My First Heading" } ] }, { "start": 60, "end": 65, "type": "Text", "raw": "\n ", "data": "\n " }, { "start": 65, "end": 91, "type": "Element", "name": "p", "attributes": [], "children": [ { "start": 68, "end": 87, "type": "Text", "raw": "My first paragraph.", "data": "My first paragraph." } ] }, { "start": 91, "end": 92, "type": "Text", "raw": "\n", "data": "\n" } ] }, { "start": 99, "end": 101, "type": "Text", "raw": "\n\n", "data": "\n\n" } ] } ] } ```Background
Option 1 - Build and pass atoms
My current approach is based on the large objects guide. Each node is a React component that is responsible for creating and memoizing sub-atoms.
Option 2 - Using scope / provider
I haven't been able to figure out if this is a good idea or not. The idea is to simplify working on nested editors, since they should be able to know about atoms in their context. The idea here is that other things can connect into the sub-atom state, without having to touch the React hooks.
My big unanswered questions are:
Provider
?get
method to read from a different store?Here's a non-working example that's trying to demonstrate the scoping/context idea.
The text was updated successfully, but these errors were encountered: