-
-
Notifications
You must be signed in to change notification settings - Fork 649
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
[Question] Best practices for large objects? #255
Comments
OK, this would be a good example for discussion. Thanks for opening this up. Please everyone join. Some meta answers for a start.
I will write some more thoughts later (maybe starting with lists.) A quick question, does the objects in arrays have ID? Like what you would specify to JSX |
@dai-shi thank you for the quick response! The objects in arrays do not have ids in this example, but they very well could have (either a real id from the server, or an assigned id on the client side). One things that comes to mind is using https://github.com/paularmstrong/normalizr to turn the deeply nested object into a flatter structure. I fail to see how that would help with I'm very interested in the recommended patterns for lists and trees. |
So, there are mainly three approaches.
a big atomThis idea is not very atomic, but it works. This is useful if we need to persist data or push back to the server. const dataAtom = atom({
people: [
{ name: 'Luke Skywalker', height: '172' },
{ name: 'C-3PO', height: '167' },
],
films: [
{ title: 'A New Hope', episode_id: 4, planets: [{ name: 'Tatooine' }, { name: 'Alderaan' }] },
{ title: 'The Empire Strikes Back', episode_id: 5, planets: [{ name: 'Hoth' }] },
],
}
const peopleAtom = atom(get => get(dataAtom).people)
const filmsAtom = atom(get => get(dataAtom).films)
const createFilmAtom = index => atom(get => get(filmsAtom)[index]) (Wait a minute, I might notice a missing optimization in the current implementation...) There are utility functions to ease this pattern: normalized dataLike you implied normalizr. const peopleMapAtom = atom({
p1: { name: 'Luke Skywalker', height: '172' },
p2: { name: 'C-3PO', height: '167' },
})
const planetsMapAtom = atom({
a1: { name: 'Tatooine' },
a2: { name: 'Alderaan' },
a3: { name: 'Hoth' },
})
const filmsMapAtom = atom({
f1: { title: 'A New Hope', episode_id: 4, planets: ['a1', 'a2'] },
f2: { title: 'The Empire Strikes Back', episode_id: 5, planets: ['a3'] },
}) There are utility functions to ease this pattern: atom referencesThis looks crazy, but it works. const dataAtom = atom({
people: atom([
atom({ name: 'Luke Skywalker', height: '172' }),
atom({ name: 'C-3PO', height: '167' }),
]),
films: atom([
atom({ title: 'A New Hope', episode_id: 4, planets: [atom({ name: 'Tatooine' }), atom({ name: 'Alderaan' })] }),
atom({ title: 'The Empire Strikes Back', episode_id: 5, planets: [atom({ name: 'Hoth' })] }),
],
} This is pseudo code. We don't manually create like this. To support this pattern, atom returns a unique string, so you can specify it to I would personally would like to recommend the third pattern. It's pretty much like jotai, the power of it. Having nested atoms is tricky, though. I wonder how DX would be like. TS is almost necessary for this. The first pattern would be good for persistence or server cache. The second one might be easier to understand, especially with |
The third pattern looks interesting. Could you demonstrate what it might look like in an actual implementation? Thanks! 😄 |
@sandren Both are just lists, not trees. |
Hi @dai-shi - I've been experimenting with some of these patterns in one of my applications, and I had a question wrt I have discovered const interactiveLayersAtom = atom(get => get(layersAtom).filter(id =>
get(focusAtom(layerAtomFamily({ id }), optic => optic.prop("interactive")))
)) Admittedly, this seems a little convoluted, but it does work. Any suggestions, pointing in the right direction is much appreciated. Thanks again for your amazing work. |
There’re probably several ways. What would be easy for your current setup is selectAtom with equalityFn. But, that requires to create an atom holding filtered list of atoms. So, the way you come up with is not that bad. This is jotai puzzle. 🤔 Do you want only solutions to keep using atomFamily? |
Updated #255 (comment) . |
Thanks for the quick reply. I'm always astonished by both your knowledge and promptness! I am just experimenting with patterns so I have no constraints to keep using const interactiveLayersAtom = atom(get =>
get(layersAtom).flatMap(layerAtom => {
const modAtom = selectAtom(
layerAtom,
({ id, interactive }) => ({
id,
interactive,
}),
(a, b) => a.id === b.id && a.interactive === b.interactive
)
const { interactive, id } = get(modAtom)
return interactive ? [id] : []
})
) But the issue I'm running into with this is that I don't have the ability to set individual layers. I think I'm creating an anti-pattern. Here is the hook I have that creates an atom and pushes to an array. Calling const useLayer = params => {
const { visible = true, interactive = true, source, label, id } = params
const [, setLayers] = useLayers()
const layerAtom = atom({ id, source, label, visible, interactive })
const [layer, setLayer] = useImmerAtom(layerAtom)
useEffect(() => {
setLayers(state => void state.push(layerAtom))
}, [])
console.log('This does not update when setLayer called in component', layer)
return [layer, setLayer]
} I'm guessing I should just be creating another atom that pulls and returns the atom from the list? |
It sounds like there's some misunderstanding.
Yeah. But, with # 3, I wouldn't use
You can't create atom in render. If necessary, use Let's try something simple. const itemsAtom = atom([]) // array of atoms
const createItem = (param) => atom(param) // create `itemAtom`
const addItem = atom(
null,
(_get, set, itemAtom) => {
set(itemsAtom, (prev) => [...prev, itemAtom])
}
)
const interactiveItemsAtom = atom(
(get) => get(itemsAtom).filter((itemAtom) => get(itemAtom).interactive)
) Is this readable? We can change |
Ah, duh. That pattern is definitely simple / readable. Thanks for simplifying all my trials! One thing is that |
Ah, that was the original concern. It's possible with some tricks. const interactiveAtomCache = new WeakMap()
const getInteractiveAtom = (itemAtom) => {
if (!interactiveAtomCache.has(itemAtom)) {
interactiveAtomCache.set(itemAtom, atom((get) => get(itemAtom).interactive))
}
return interactiveAtomCache.get(itemAtom)
}
const interactiveItemsAtom = atom(
(get) => get(itemsAtom).filter((itemAtom) => get(getInteractiveAtom(itemAtom)))
) (There might be a better way. |
Nice! I suppose that I could also use |
Neither const interactiveItemsAtom = atom(
(get) => get(itemsAtom).filter((itemAtom) => get(atom((get) => get(itemAtom).interactive)))
) |
Yep, that works nicely. The trick is wrapping it in its own |
Hi all,
So far we are loving
jotai
. Amazing work ❤️.I'd like to start a conversation around best practices for larger objects. Let's say our back-end gives us a fairly large object which would be mutable at different levels of the object.
Now our React component tree has
<Car />
with a list of<Parts />
, which has a list of<Factory />
s.What would be the recommended way to go about
Using
jotai
? I am personally very familiar with MobX, where this would be one large observable object (or class instance). However, I am not sure how to look at this in atoms.Would each car be an atom, with parts turned into atoms and factory locations into atoms? Any other approaches?
I'd love to hear thoughts on this! 😊
The text was updated successfully, but these errors were encountered: