Skip to content
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

Does this allow async computed atoms like jotai does? #2

Open
pyrossh opened this issue Oct 10, 2020 · 4 comments
Open

Does this allow async computed atoms like jotai does? #2

pyrossh opened this issue Oct 10, 2020 · 4 comments

Comments

@pyrossh
Copy link

pyrossh commented Oct 10, 2020

I would like to try doing something like this,

const sumAtom = atom(async get => {
  get(atomOne)
  return await fetch("/api");
})
@merisbahti
Copy link
Owner

merisbahti commented Oct 10, 2020

Hi Peter,

Short answer: nope, not yet.

Long answer:

It's interesting to think about how we would go about doing this.

In jotai's case, what they do is for derived atoms, they use suspense, so the suspense boundary renders the placeholder (loading, spinner, etc) while the atom has no value.

Klyva exists outside of react, so it has no concept of suspense.

Also, in Klyva, so far, all atoms always have a value, for async functions, this would not be the case. So how would we model this in klyva?

One suggestion would be, that if the atom is async, the atom returns a union value, where the value can be in different states: loading | resolved | error

I can make a PR for this.

What we could then do, in the future, is that if useAtom is used on a async atom, it uses suspense so the user does not have to worry about the different states.

@pyrossh
Copy link
Author

pyrossh commented Oct 10, 2020

Hi Meris,

Thanks for the quick reply. The union value makes sense. I was trying to figure out a way to do that (was thinking of finding if it was a function and throwing the promise). The idea that the atom can be updated outside the react ecosystem is actually a very common use case in UI.
I tried to use derived atoms and ran into this typescript error,

const atomOne = atom(10)
const atomTwo = atom(20)
const sumAtom = atom((get) => get(atomOne) + get(atomTwo))

function App() {
  const d = useAtom(atomOne)
  const g = useAtom(sumAtom)
  return (
    <div className="App">
      {d}
      {'-'}
      {g}
    </div>
  );
}

Error:

  > 12 |   const g = useAtom(sumAtom)
Argument of type 'ReadOnlyAtom<number>' is not assignable to parameter of type 'Atom<number>'.
  Property 'update' is missing in type 'ReadOnlyAtom<number>' but required in type 'Atom<number>'.  TS2345

@merisbahti
Copy link
Owner

merisbahti commented Oct 10, 2020

const atomOne = atom(10)
const atomTwo = atom(20)
const sumAtom = atom((get) => get(atomOne) + get(atomTwo))

function App() {
  const d = useAtom(atomOne)
  const g = useAtom(sumAtom)
  return (
    <div className="App">
      {d}
      {'-'}
      {g}
    </div>
  );
}

Hi, first of all, thanks for the report, it has been fixed here: 2ccdc97
And will be working in 0.1.6 (see here: https://www.npmjs.com/package/klyva).

The idea that the atom can be updated outside the react ecosystem is actually a very common use case in UI.

Yes, it feels like such a normal case, and this optimizes for that.

@pyrossh
Copy link
Author

pyrossh commented Oct 10, 2020

Thanks it works now.
I had built a simple use-promise library to work with react suspense.
https://github.com/pyros2097/use-promise/blob/master/index.js
using the same concept i just ported useAtom to handle suspense for now to test out whether it will work and it does. The typing doesn't work though.

import React from 'react'
import { ReadOnlyAtom } from './types'
const promiseCache = new Map<any, any>()

export const useAtom = <T>(atom: ReadOnlyAtom<T>) => {
  const [cache, setCache] = React.useState(atom.getValue())
  React.useEffect(() => {
    const unsub = atom.subscribe(value => {
      promiseCache.delete(cache);
      setCache(value)
    })
    return unsub
  }, [atom, cache])
  if (cache instanceof Promise) {
    const v = promiseCache.get(cache)
    if (v) {
      if (v instanceof Error) {
        throw v
      } else {
        return promiseCache.get(cache)
      }
    }
    cache
      .then((res) => {
        promiseCache.set(cache, res)
      })
      .catch((err) => {
        promiseCache.set(cache, err)
      });
    throw cache
  }
  return cache
}
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import { atom } from 'klyva'
import { useAtom } from './utils';

const atomOne = atom(1)
const atomTwo = atom(10)
const sumAtom = atom((get) => get(atomOne) + get(atomTwo))

type Todo = {
  complete: Boolean
  title: String
}

const todoAtom = atom<Promise<Todo>>(async (get) => {
  const one = get(atomOne)
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/" + one);
  return await res.json()
})

function App() {
  const a = useAtom(atomOne)
  const sum = useAtom(sumAtom)
  const todo = useAtom(todoAtom)
  return (

    <div className="App" onClick={() => atomOne.update((v) => v + 1)}>
      {a}
      {'-'}
      {sum}
      {'-'}
      {todo.title}
    </div>
  );
}

ReactDOM.render(
  <React.StrictMode>
    <Suspense fallback={<div>loading...</div>}>
      <App />
    </Suspense>
  </React.StrictMode>,
  document.getElementById('root')
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants