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

Is it possible to read an atom's current state without using useAtom? #60

Closed
tushar-upadhyay opened this issue Sep 21, 2020 · 23 comments · Fixed by #140
Closed

Is it possible to read an atom's current state without using useAtom? #60

tushar-upadhyay opened this issue Sep 21, 2020 · 23 comments · Fixed by #140
Labels
has snippet This issue includes code snipppets as solutions or workarounds

Comments

@tushar-upadhyay
Copy link

Suppose i am storing some authentication state of a user in an atom and i want to call an API with that state. So how can i do that outside the functional component?

@tushar-upadhyay tushar-upadhyay changed the title Is it possible to read a atom current state without using useAtom? Is it possible to read an atom's current state without using useAtom? Sep 21, 2020
@znk
Copy link

znk commented Sep 21, 2020

Quotting from #13, whether it's not possible or not covered by this library:

Jotai state is within React component tree.

If you want to update state outside React, zustand works better.

@lishine
Copy link

lishine commented Sep 21, 2020

But what about atom.read, atom.write ?

@dai-shi
Copy link
Member

dai-shi commented Sep 21, 2020

But about atom.read, atom.write ?

They are just definitions, so you can't call them.

whether it's not possible or not covered by this library

Correct. Just think it like the normal React.useState().

So, here's the workaround:

const Component = () => {
  const authStateRef = useRef()
  const authState = useAtom(authAtom) // or useState(...)
  useEffect(() => {
    authStateRef.current = authState
  })
  return (
    <Child authStateRef={authStateRef} />
  )
  // or, use Context to pass authStateRef.
}

So how can i do that outside the functional component?

Ah, outside component... I missed that, so it's like this:

let globalAuthState
const Component = () => {
  const authState = useAtom(authAtom) // or useState(...)
  useEffect(() => {
    globalAuthState = authState
  })
  return ...
}

But, as you may notice, this naive pattern doesn't seem like good practice.
The point is that useEffect is the method to connect React world and outside world.

@tushar-upadhyay
Copy link
Author

So that mean, we cannot seperate buisness logic with the component right?

@dai-shi
Copy link
Member

dai-shi commented Sep 22, 2020

You could make a custom hook, but it depends on React. If that's what you mean, that's correct.

@lxsmnsyc
Copy link

If you want a jotai-like decoupling from React, you might want to read this article: https://bennetthardwick.com/blog/recoil-js-clone-from-scratch-in-100-lines/

@dai-shi
Copy link
Member

dai-shi commented Sep 29, 2020

While I was ideating the persistence pattern #4 (comment), I came up with this idea.

const callbackAtom = atom(null, (get, set, action) => {
  action.callback(get(action.atom))
})

  const [, getAtom] = useAtom(callbackAtom)
  getAtom({
    atom: anAtom,
    callback: (value) => { console.log(value) /* or whatever */ },
  })

It will only allow to read committed state, so it's essentially the same as:

  useEffect(() => {
    globalAuthState = authState
  })

in #60 (comment).

Just found https://recoiljs.org/docs/api-reference/core/useRecoilCallback is a bit similar, or not.


@tushar-upadhyay Would you tell us your use case? Maybe we can create a custom atom and/or custom hook.

@dai-shi dai-shi added has snippet This issue includes code snipppets as solutions or workarounds enhancement New feature or request needs info Further information is requested and removed enhancement New feature or request labels Oct 4, 2020
@x4080
Copy link

x4080 commented Oct 9, 2020

@dai-shi

I use below in recoil

const saveForm = useRecoilCallback(({snapshot}) => () => {
        theValues = snapshot.getLoadable(atomForm).contents
        ...

I'm using recoil for form state, the listener is inside the components inside the form component, and in this save form function, I can get the value of the form state without needed to listen for form state all the time in the form component - it makes a lot of different in my case

Can we got something like this ?

Thanks

@dai-shi
Copy link
Member

dai-shi commented Oct 9, 2020

@x4080
Hi, I'm still not sure what useRecoilCallback exactly does, so correct me if I'm wrong.

Here's what I come up with for your use case.

const saveFormAtom = atom(null, (get, set, [args, callback]) => {
  // get, set...
  const result = ...
  callback(result)
})

const [, doSaveForm] = useAtom(saveFormAtom)
const saveForm = (args) => new Promise((resolve) => {
  doSaveForm([args, resolve])
})

  const result = await saveForm(...)

@x4080
Copy link

x4080 commented Oct 9, 2020

@dai-shi Thanks again, the useRecoilCallback create a function that can get the latest value of the atom, without having to userecoil listener, the benefit is theres no rerender at all to get the value

Sorry if im not clear, if we can get the value stored by jotai without actively listening to it (listener is in other component) then it will be perfect

@x4080
Copy link

x4080 commented Oct 9, 2020

oh i just remember, the atomform content is the field contents of the form which is listened in other component for editing the fields

@dai-shi
Copy link
Member

dai-shi commented Oct 9, 2020

So, we want a way to read the latest value without using it. So again, it's like this:

const callbackAtom = atom(null, (get, _set, callback) => {
  const result = get(...)
  callback(result)
})

const [, cb] = useAtom(callbackAtom)
const callback = () => new Promise(cb)

Are we on the same page? Maybe you want to show us a small working (non-working) example.

@x4080
Copy link

x4080 commented Oct 9, 2020

im on mobile right now, ill create some thing to test later thanks

@x4080
Copy link

x4080 commented Oct 9, 2020

@dai-shi I got it to work now, using your example

const callbackAtom = atom(null, (get, set, action) => {
  action.callback(get(action.atom))
})

  const [, getAtom] = useAtom(callbackAtom)
  getAtom({
    atom: anAtom,
    callback: (value) => { console.log(value) /* or whatever */ },
  })

My implementation of save form look like this

const atomForm = atom(_formDefaultValues);
const callbackAtom = atom(null, (get, set, action) => {
    action.callback(get(action.atom))
})


... in component
const [, cb] = useAtom(callbackAtom)
const saveForm = () => {
        cb({
            atom: atomForm,
            callback: (value) => { console.log(value) },
        })
    }

Maybe the callbackAtom shoud be added to utility too ? It is useful tool

Thanks for everything

@dai-shi
Copy link
Member

dai-shi commented Oct 9, 2020

Yes, it'd be nice to add something in utils, because the impl is not very obvious.
I have a few questions:

  • Would you want to read multiple atom values in a callback?
  • Would you want to update atom values in a callback?
  • Would you want to return a promise?

@dai-shi
Copy link
Member

dai-shi commented Oct 9, 2020

Here's a draft:

const useAtomCallback = <Arg, Result>(
  callback: (get: Getter, set: Setter, arg: Arg) => Result
): (arg: Arg) => Promise<Result> => {
  const anAtom = useMemo(() => atom(
    null,
    (get, set, [arg, resolve, reject]) => {
      try {
        resolve(callback(get, set, arg))
      } catch (e) {
        reject(e)
      }
    }
  ), [callback])
  const [, invoke] = useAtom(anAtom)
  return useCallback((arg) => new Promise((resolve, reject) => {
    invoke([arg, resolve, reject])
  }), [invoke])
}

Is anyone interested in making this real??

@dai-shi dai-shi added the help wanted Please someone help on this label Oct 9, 2020
@x4080
Copy link

x4080 commented Oct 10, 2020

Yes, read multiple atom at once is pretty important

@dai-shi
Copy link
Member

dai-shi commented Oct 10, 2020

@x4080 Would you try useAtomCallback above, and see if it works for your use case?

@x4080
Copy link

x4080 commented Oct 12, 2020

@dai-shi Sorry for late reply, I'm not using typescript, so in plain javascript your function would be

const useAtomCallback =
    callback((get, set, arg) => {
        const anAtom = useMemo(() => atom(
            null,
            (get, set, [arg, resolve, reject]) => {
                try {
                    resolve(callback(get, set, arg))
                } catch (e) {
                    reject(e)
                }
            }
        ), [callback])
        const [, invoke] = useAtom(anAtom)
        return useCallback((arg) => new Promise((resolve, reject) => {
            invoke([arg, resolve, reject])
        }), [invoke])
    })

Is that correct ? And how do I use it using my example above ?

... in component
const [, cb] = useAtom(callbackAtom)
const saveForm = () => {
        cb({
            atom: atomForm,
            callback: (value) => { console.log(value) },
        })
    }

And can it get multiple atom @once ?

Thanks

@dai-shi
Copy link
Member

dai-shi commented Oct 12, 2020

@x4080

The JS version would be:

const useAtomCallback = (callback) => {
  const anAtom = useMemo(() => atom(
    null,
    (get, set, [arg, resolve, reject]) => {
      try {
        resolve(callback(get, set, arg))
      } catch (e) {
        reject(e)
      }
    }
  ), [callback])
  const [, invoke] = useAtom(anAtom)
  return useCallback((arg) => new Promise((resolve, reject) => {
    invoke([arg, resolve, reject])
  }), [invoke])
}

You would use it like this:

// in component
const saveForm = useAtomCallback(useCallback((get) => {
  return get(atomForm) + get(anotherAtom)
}), []))

const result = await saveForm()

@dai-shi dai-shi removed the needs info Further information is requested label Oct 13, 2020
@x4080
Copy link

x4080 commented Oct 13, 2020

@dai-shi Yes it works perfectly thanks

@dai-shi
Copy link
Member

dai-shi commented Oct 13, 2020

@x4080 Thanks for confirming!


Is anyone interested in drafting a PR for this new ./src/utils/useAtomCallback.ts ?
I'm not so sure about the hook name and open for proposals.

@dai-shi
Copy link
Member

dai-shi commented Oct 17, 2020

Well, I'm working on it now.

@dai-shi dai-shi removed the help wanted Please someone help on this label Oct 17, 2020
@dai-shi dai-shi mentioned this issue Oct 17, 2020
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has snippet This issue includes code snipppets as solutions or workarounds
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants