dai-shi / use-atom

Yet another implementation for Jotai atoms without side effects
MIT License
109 stars 0 forks source link

Use multiple atoms with one hook #5

Closed natew closed 2 years ago

natew commented 4 years ago

Would this be possible in order to reduce having a ton of hooks? Something like:

const [[atomAValue, atomASet],  ...] = useAtoms(atomA, atomB, atomC)

Edit: accidentally hit submit early...

Ideally, better would be to not have it "subscribe" to an atom unless called for (this is continuing off previous discussion), could be something like this:

const [get, set] = useAtoms()
get(atomA)
set(atomA, value)
natew commented 4 years ago

Actually, went further into it and I'd need atomFamily/selectorFamily to support this either way.

dai-shi commented 4 years ago

WhileuseAtoms would be something that can only be implemented in core, I would like to understand its use case.

Would this be possible in order to reduce having a ton of hooks?

If you already know concrete atoms, you can just create a custom hook. So, I assume you would like to do like

const ... = useAtoms([atomA, atomB, ...]);
// or useAtoms(atomA, atomB, ...)

But then how would you use the result?

  1. map result to create react elements -> useAtom in child components
  2. reduce result to create a value -> use deriveAtom?
  3. ...or something else?

Thanks for raising this issue for discussion!

natew commented 4 years ago

The basic use case is to wrap this in a proxy;

class Store {
  x = 1
  get y() { return this.x+ 1 }
}

function Component() {
   const store = useStore(Store) // proxifies and turns all values into atoms, all get values into selectors
  // tracks access only on access
  store.y

  useEffect(() => {
    store.x = 10 // sets the atom
  }, [])
}

Note, you’d need to access a dynamic amount of atoms per render, and it could change. This the need for a syntax where I don’t have more than one hook.

Second part is that I’d like to be able to have different namespaces for stores. A lot like a selectorFamily/atomFamily but at the store level:

class Store {
  x = 1
  get y() { return this.x+ 1 }
}

function Component() {
   const store = useStore(Store, { id: 1 }) // gets a unique idempotent store
  // ...
}
dai-shi commented 4 years ago

I'm not 100% convinced if your use case matches with the atom abstraction. I would probably create a single state for a store and use use-context-selector.

That said, I'm not against to create useAtoms for possible use cases.

const [[atomAValue, atomASet],  ...] = useAtoms([atomA, atomB, atomC]);

Does this look OK? Do you know if Recoil provides such primitive hook?

Ideally, better would be to not have it "subscribe" to an atom unless called for

Even with this API, you can do this: Just only pass atoms you want to subscribe.

(On second thought, I'm not sure how the implementation is easy.)

grumd commented 2 years ago

I don't think it's a good idea. If you did something like this (dynamically change the array)

const [...] = useAtoms(isUsingB ? [atomA, atomB] : [atomA]);

Then you'd be breaking the rules of hooks. Allowing an API like useAtoms doesn't seem like a good idea to me.

And if you don't need to change the atoms array on the fly, then just do a custom hook:

const useMyAtoms = () => {
  const a = useAtom(atomA);
  const b = useAtom(atomB);
  const c = useAtom(atomC);
  return [a, b, c];
};

const Component = () => {
  const [[], [], []] = useMyAtoms();
}
dai-shi commented 2 years ago

Oops, this is an old issue. Forgot to close it.

In Jotai API, we do this:

const Component = () => {
  const [value1, value2, value3] = useAtomValue(useMemo(() => atom(
    (get) => [get(atom1), get(atom2), get(atom3)]
  ), []));
  // ...
};

Then you'd be breaking the rules of hooks.

We could avoid breaking the rules. But, it's not very intuitive, DX-wise. Anyway, we don't have such a hook in jotai. Neither in use-atom.