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

There is an useMutable"Snapshot" or useProxy? #283

Closed
gersondinis opened this issue Nov 28, 2021 · 9 comments
Closed

There is an useMutable"Snapshot" or useProxy? #283

gersondinis opened this issue Nov 28, 2021 · 9 comments

Comments

@gersondinis
Copy link

gersondinis commented Nov 28, 2021

The argument of useSnapshot is the proxy state, it is possible to 'redirect' all the set method to the proxy? The idea is to get a non frozen state, and be able to mutate directly, a dedicated hook for that.

@dai-shi
Copy link
Member

dai-shi commented Nov 28, 2021

what do you mean by to redirect all the set method?

@gersondinis
Copy link
Author

Sorry. I will try to be clear, useSnapshot is read-only. And I was wondering if there is another hook that returns a read/write state. Not to replace the useSnapshot, most of times immutability is needed, but in other situations directly write would be a nice to have. (or at least a hook that returns an unfrozen state)

I have to say one more time, this library is awesome!

@gersondinis
Copy link
Author

what do you mean by to redirect all the set method?

A hook that if we try to write on the returned state, it should write on the relative path but on the proxy.

@dai-shi
Copy link
Member

dai-shi commented Nov 28, 2021

This should work for most cases:

const useMutableState = (proxyObject) => {
  const [, forceUpdate] = useReducer((c) => c + 1, 0)
  useEffect(() => subscribe(proxyObject, forceUpdate), [proxyObject])
  return proxyObject
}

The reason the library doesn't provide this is it's not perfect for concurrent react. supporting concurrent thing is my motivation to develop valtio from the beginning. it's not only for valtio, but for all my state libs.

btw, have you seen the useProxy macro?

Found a related discussion: #38
So, the mutable snapshot is a confusing concept.

const state = proxy({ count: 0 })
const Component = () => {
  const snap = useMutableSnapshot(state)
  const handleClick = () => {
    snap.count++
    snap.count++
    // should count be 1 or 2?
  }
  ...
}

@gersondinis
Copy link
Author

gersondinis commented Nov 29, 2021

Any snapshot get/read access should reflect the immutable state as it was on the re-render time. In other hand, any set/write access should be "redirected" to the proxy.
But, as you illustrated, dependent set/write access may be confusing:

const handleClick = () => {
    snap.count++
    snap.count++
    // should count be 1 or 2?
    // snap.count=0 so should always be 1.
    // for dependent write access we should still use the proxy directly. 
  } 

Assuming this two lines works behind the scenes in the same way:

snap.count++
snap.count = snap.count + 1

For me this makes sense by the name of the hook (useMutableSnapshot), but this is completely theorical, I didn't see how Valtio works, and yes, now I understand the contraints.

The hook useMutableState that you mentioned subscribes the entire proxy, so any change to the proxy will trigger a re-render, right?

@dai-shi
Copy link
Member

dai-shi commented Nov 29, 2021

For me this makes sense by the name of the hook (useMutableSnapshot), but this is completely theorical, I didn't see how Valtio works, and yes, now I understand the contraints.

I don't want to add a new hook in valtio for this reason, and want the library to provide a single usage with useSnapshot.

That said, you are free to work on it on your end. And, I'm happy to help. See #38, on-change seems promising.
If something reasonable is made, we can put it on a wiki, like this one: https://github.com/pmndrs/valtio/wiki/How-to-avoid-rerenders-manually#subscribe-and-set-local-state-conditionally

The hook useMutableState that you mentioned subscribes the entire proxy, so any change to the proxy will trigger a re-render, right?

Exactly.

@gersondinis
Copy link
Author

Yes, makes sense. Thank you a lot for everything! One more time, you made a really impressive library!

@dai-shi
Copy link
Member

dai-shi commented Jan 1, 2022

Closing this, but anyone can feel free to work on it.

@dai-shi dai-shi closed this as completed Jan 1, 2022
@chhpt
Copy link

chhpt commented Aug 27, 2022

If anyone want a mutable and reactive state, you can try this. This may not be the best practice, but it workes.

type DeriveGet = <T extends object>(proxyObject: T) => T

// you can read root state from context
const rootState = proxy({
  num: 1
})

export const useReactive = <T extends object>(
  target: (state: any) => T,
  options: {
    readonly?: boolean
  } = {}
): T => {
  const { readonly } = options
  // you can read state from context 
  const value = target(rootState)
  const [, forceUpdate] = useState([])

  useEffect(() => {
    let deriveFns

    if (typeof value === 'object' && !Array.isArray(value)) {
      deriveFns = {
        ...Object.keys(target(rootState)).reduce(
          (prev: Record<string, any>, key: string) => ({
            ...prev,
            [key]: (get: DeriveGet) => (target(get(rootState)) as any)[key]
          }),
          {}
        )
      }
    } else {
      deriveFns = {
        obj: (get: DeriveGet) => target(get(rootState))
      }
    }

    const derived = derive(deriveFns)

    subscribe(derived, () => {
      forceUpdate([])
    })
  }, [])

  return readonly ? (snapshot(value) as any) : value
}

usage

function App() {
  const state = useReactive((state) => state.num)

  // do somthing
}

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

3 participants