Giter Site home page Giter Site logo

pmndrs / valtio Goto Github PK

View Code? Open in Web Editor NEW
8.5K 8.5K 231.0 3.63 MB

๐Ÿ’Š Valtio makes proxy-state simple for React and Vanilla

Home Page: http://valtio.pmnd.rs

License: MIT License

JavaScript 3.05% TypeScript 94.98% CSS 1.97%
easy mutable react state

valtio's Introduction

valtio



npm i valtio makes proxy-state simple

Build Status Build Size Version Downloads Discord Shield

Wrap your state object

Valtio turns the object you pass it into a self-aware proxy.

import { proxy, useSnapshot } from 'valtio'

const state = proxy({ count: 0, text: 'hello' })

Mutate from anywhere

You can make changes to it in the same way you would to a normal js-object.

setInterval(() => {
  ++state.count
}, 1000)

React via useSnapshot

Create a local snapshot that catches changes. Rule of thumb: read from snapshots in render function, otherwise use the source. The component will only re-render when the parts of the state you access have changed, it is render-optimized.

// This will re-render on `state.count` change but not on `state.text` change
function Counter() {
  const snap = useSnapshot(state)
  return (
    <div>
      {snap.count}
      <button onClick={() => ++state.count}>+1</button>
    </div>
  )
}
Note for TypeScript users: Return type of useSnapshot can be too strict.

The snap variable returned by useSnapshot is a (deeply) read-only object. Its type has readonly attribute, which may be too strict for some use cases.

To mitigate typing difficulties, you might want to loosen the type definition:

declare module 'valtio' {
  function useSnapshot<T extends object>(p: T): T
}

See #327 for more information.

Note: useSnapshot returns a new proxy for render optimization.

Internally, useSnapshot calls snapshot in valtio/vanilla, and wraps the snapshot object with another proxy to detect property access. This feature is based on proxy-compare.

Two kinds of proxies are used for different purposes:

  • proxy() from valtio/vanilla is for mutation tracking or write tracking.
  • createProxy() from proxy-compare is for usage tracking or read tracking.
Use of this is for expert users.

Valtio tries best to handle this behavior but it's hard to understand without familiarity.

const state = proxy({
  count: 0,
  inc() {
    ++this.count
  },
})
state.inc() // `this` points to `state` and it works fine
const snap = useSnapshot(state)
snap.inc() // `this` points to `snap` and it doesn't work because snapshot is frozen

To avoid this pitfall, the recommended pattern is not to use this and prefer arrow function.

const state = proxy({
  count: 0,
  inc: () => {
    ++state.count
  },
})

If you are new to this, it's highly recommended to use eslint-plugin-valtio.

Subscribe from anywhere

You can access state outside of your components and subscribe to changes.

import { subscribe } from 'valtio'

// Subscribe to all state changes
const unsubscribe = subscribe(state, () =>
  console.log('state has changed to', state),
)
// Unsubscribe by calling the result
unsubscribe()

You can also subscribe to a portion of state.

const state = proxy({ obj: { foo: 'bar' }, arr: ['hello'] })

subscribe(state.obj, () => console.log('state.obj has changed to', state.obj))
state.obj.foo = 'baz'

subscribe(state.arr, () => console.log('state.arr has changed to', state.arr))
state.arr.push('world')

To subscribe to a primitive value of state, consider subscribeKey in utils.

import { subscribeKey } from 'valtio/utils'

const state = proxy({ count: 0, text: 'hello' })
subscribeKey(state, 'count', (v) =>
  console.log('state.count has changed to', v),
)

There is another util watch which might be convenient in some cases.

import { watch } from 'valtio/utils'

const state = proxy({ count: 0 })
const stop = watch((get) => {
  console.log('state has changed to', get(state)) // auto-subscribe on use
})

Suspend your components

Valtio supports React-suspense and will throw promises that you access within a components render function. This eliminates all the async back-and-forth, you can access your data directly while the parent is responsible for fallback state and error handling.

const state = proxy({ post: fetch(url).then((res) => res.json()) })

function Post() {
  const snap = useSnapshot(state)
  return <div>{snap.post.title}</div>
}

function App() {
  return (
    <Suspense fallback={<span>waiting...</span>}>
      <Post />
    </Suspense>
  )
}

Holding objects in state without tracking them

This may be useful if you have large, nested objects with accessors that you don't want to proxy. ref allows you to keep these objects inside the state model.

See #61 and #178 for more information.

import { proxy, ref } from 'valtio'

const state = proxy({
  count: 0,
  dom: ref(document.body),
})

Update transiently (for often occurring state-changes)

You can read state in a component without causing re-render.

function Foo() {
  const { count, text } = state
  // ...

Or, you can have more control with subscribing in useEffect.

function Foo() {
  const total = useRef(0)
  useEffect(() => subscribe(state.arr, () => {
    total.current = state.arr.reduce((p, c) => p + c)
  }), [])
  // ...

Update synchronously

By default, state mutations are batched before triggering re-render. Sometimes, we want to disable the batching. The known use case of this is <input> #270.

function TextBox() {
  const snap = useSnapshot(state, { sync: true })
  return (
    <input value={snap.text} onChange={(e) => (state.text = e.target.value)} />
  )
}

Dev tools

You can use Redux DevTools Extension for plain objects and arrays.

import { devtools } from 'valtio/utils'

const state = proxy({ count: 0, text: 'hello' })
const unsub = devtools(state, { name: 'state name', enabled: true })
Manipulating state with Redux DevTools The screenshot below shows how to use Redux DevTools to manipulate state. First select the object from the instances drop down. Then type in a JSON object to dispatch. Then click "Dispatch". Notice how it changes the state.
image

Use it vanilla

Valtio is not tied to React, you can use it in vanilla-js.

import { proxy, subscribe, snapshot } from 'valtio/vanilla'
// import { ... } from 'valtio/vanilla/utils'

const state = proxy({ count: 0, text: 'hello' })

subscribe(state, () => {
  console.log('state is mutated')
  const obj = snapshot(state) // A snapshot is an immutable object
})

useProxy util

While the separation of proxy state and its snapshot is important, it's confusing for beginners. We have a convenient util to improve developer experience. useProxy returns shallow proxy state and its snapshot, meaning you can only mutate on root level.

import { useProxy } from 'valtio/utils'

const state = proxy({ count: 1 })

const Component = () => {
  // useProxy returns a special proxy that can be used both in render and callbacks
  // The special proxy has to be used directly in a function scope. You can't destructure it outside the scope.
  const $state = useProxy(state)
  return (
    <div>
      {$state.count}
      <button onClick={() => ++$state.count}>+1</button>
    </div>
  )
}

Computed properties

You can define computed properties with object getters.

const state = proxy({
  count: 1,
  get doubled() {
    return this.count * 2
  },
})

Consider it as an advanced usage, because the behavior of this is sometimes confusing.

For more information, check out this guide.

proxyWithHistory util

This is a utility function to create a proxy with snapshot history.

import { proxyWithHistory } from 'valtio-history'

const state = proxyWithHistory({ count: 0 })
console.log(state.value) // ---> { count: 0 }
state.value.count += 1
console.log(state.value) // ---> { count: 1 }
state.undo()
console.log(state.value) // ---> { count: 0 }
state.redo()
console.log(state.value) // ---> { count: 1 }

proxySet util

This is to create a proxy which mimic the native Set behavior. The API is the same as Set API

import { proxySet } from 'valtio/utils'

const state = proxySet([1, 2, 3])
//can be used inside a proxy as well
//const state = proxy({
//    count: 1,
//    set: proxySet()
//})

state.add(4)
state.delete(1)
state.forEach((v) => console.log(v)) // 2,3,4

proxyMap util

This is to create a proxy which emulate the native Map behavior. The API is the same as Map API

import { proxyMap } from 'valtio/utils'

const state = proxyMap([
  ['key', 'value'],
  ['key2', 'value2'],
])
state.set('key', 'value')
state.delete('key')
state.get('key') // ---> value
state.forEach((value, key) => console.log(key, value)) // ---> "key", "value", "key2", "value2"

Compatibility

Valtio works with React with hooks support (>=16.8). It only depends on react and works with any renderers such as react-dom, react-native, react-three-fiber, and so on.

Valtio works on Node.js, Next.js and other frameworks.

Valtio also works without React. See vanilla.

Plugins

Recipes

Valtio is unopinionated about best practices. The community is working on recipes on wiki pages.

valtio's People

Contributors

ahaoboy avatar alexander-entin avatar aslemammad avatar aulneau avatar barelyhuman avatar dai-shi avatar devsdk avatar drcmda avatar fkhadra avatar iinfin avatar italodeandra avatar kitten avatar loganvolkers avatar noitidart avatar pastelmind avatar rikuvan avatar roguesherlock avatar romulo94 avatar sarimarton avatar sepehr-safari avatar shairez avatar soilspoon avatar stephenh avatar steve8708 avatar thelinuxlich avatar theomjones avatar tmkx avatar tometo-dev avatar troywith77 avatar ypazevedo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

valtio's Issues

subscribeKey doesn't work on vanilla version

When importing the vanilla version of valtio, subscribeKey doesn't work. It always gives Error('Please use proxy object').

import { proxy, subscribe, snapshot } from 'valtio/vanilla';
import { subscribeKey } from 'valtio/utils';

let state = proxy({
    name: 'Suzy',
});

subscribeKey(state, 'name', () => {});  // error

I was able to copy-paste the subscribeKey function into my file, instead of importing it, and then it worked.

I also added the notifyInSync variable which is missing from subscribeKey:

// this pasted into the above file, makes it work
export const subscribeKey = <T extends object>(
    proxyObject: T,
    key: keyof T,
    callback: (value: T[typeof key]) => void,
    notifyInSync?: boolean,
) => {
    let prevValue = proxyObject[key];
    return subscribe(proxyObject, () => {
        const nextValue = proxyObject[key];
        if (!Object.is(prevValue, nextValue)) {
            callback((prevValue = nextValue));
        }
    }, notifyInSync);
};

Thoughts about SSR

This is not an issue but just some thoughts I wanted to share.

I saw the design of valtio a few days ago on twitter and liked it a lot. Sure, you still need to work carefully with anything that updates the state or you will very quickly land in where-did-my-state-update-from-hell ๐Ÿ˜…

Anyways - one thing that stopped my mind for a second was: how would this work in SSR?
The problem is that one cannot just have a global state object hanging somewhere in a JS file to be imported and manipulated, because in SSR its a bit like you have the app running several times in parallel but each one needs a separate state.

I had the idea that this can be accomplished by wrapping valtios proxy and useProxy just a bit and routing the state object through a context.

Here is a CodeSandbox that runs both in SSR as well as in the browser:

https://codesandbox.io/s/mystifying-heyrovsky-eu0ek?file=/src/App.js

In SSR, the state remains a plain object. Its important that all data necessary to render the app are fetched before starting to render.

When the app notices its running in a browser context, the state will be "activated" and becomes a proxy object that can be manipulated at will and causes UI updates. :)

This is just a rough idea written down in a couple of minutes - but in theory it should also work with a nextJS app.

Improve error message when accidentally passing non proxies

Currently, if one passes an non-proxy object to a public API like subscribe that expects a proxy object, it will raise an error like Cannot read property Symbol() of undefined or something of undefined. To improve developer experience, a more meaningful error message would be nice.

Thoughts on middleware?

Learned about this from Jack Herrington's youtube video from about a month ago.

This looks really cool! From what I gathered in your docs and the aforementioned video, valtio satisfies all these conditions

  • Manage shared state
  • Allow for subscriptions from React
  • Allow for mutation from React
  • Allow for subscriptions from nonโ€”React
  • Allow for mutation from nonโ€”React
  • Allow for methods on the global state
  • Allow for derived data on the global state
  • Optimized reโ€”renders

Wondering if there are plans for middleware for this to enable add-ons that could cover

  • State/Mutation logging
  • Time travel debugging

I imagine it'd be great for tools like or custom solutions log rocket to be able to record sessions state mutations
Thoughts on supporting middleware? or if you do including that in the readme?

Assigning directly to an array by index does not change array length

Expected behavior

When assigning directly to an array position, the array length should be updated, a la the native JavaScript array behavior. Any components depending on the array (for example, iterating over its items) should be re-rendered.

Example of index assignment with a native Array

const arr = [];
arr[0] = 'foo';
arr.length; // 1
arr[6] = 'bar';
arr.length; // 7

Observed behavior

When assigning to an array position on a proxy store, the length is not updated, and dependent components do not re-render. However, an update notification is sent to subscribe.

Reproduction: https://codesandbox.io/s/valtio-array-length-bug-kl8d2?file=/src/App.js

Uncomment line 44 to "fix" the problem manually by assigning the correct array length. This triggers the component re-render, too.

Observe also that the subscribe callback, when it logs the current proxy state, shows the new third item present but the length is still 2.

How can I use controlled Components with valtio?

With useState you can have a controlled component with no extra worries, but with valtio proxied variables, if I map the state.variable to the value property of the component and the snapshot.variable to the onChange event, weird behavior happens(textareas, for example, when you type the cursor drops to the end of the text on every change)

Consider creating an eslint rule/plugin

As mentioned on Twitter, the fact that one has to read from another variable than they are writing to, is quite confusing.

I understand the reasoning behind it. But that reasoning doesn't change the developer experience.

Hence I suggest to consider creating an eslint plugin. So that the developer will be protected from accidental mistakes.

This would be similar to what the react eslint plugin does for misused hooks (think dependency array / conditionally calling of hooks).

[Umbrella] Improve tests

I'd admit the current tests might not cover all use cases.
To make the library more stable for the future, adding tests would be great.
That would also clarify what is supported, and what is not supported but works for now.

Feature request: Watch

watch (pending name) is a proposed function exportable from valtio that allows automatically tracking valtio instances instead of explicitly declaring the valtio instances you need to watch (subscribe) for. This is similar to useEffect except that you don't have to explicitly define the dependency list.

This proposed function allows you to easily compose subscriptions to valtio instances and not to individually watch them (subscribe).

Example

const count = proxy({ value: 0 });

const stop = watch(() => {
  // We mark `count` as our dependency. This
  // callback will then only run whenever count updates.
  console.log(`Count: ${count.value}`);
  
  return () => {
    // We may perform cleanups here when `stop` is called.
  };
});

count.value++; // Count: 1

Multiple subscriptions:

const recipient = proxy({ value: 'Dai Shi' });
const greeting = proxy({ value: 'Hello' });

watch(() => {
  console.log(`${greeting.value}, ${recipient.value}`);
});

recipient.value = 'John Doe'; // Hello, John Doe
greeting.value = 'Bonjour'; // Bonjour, John Doe

Thank you

Hey, there should be a dedicated place to give kudos! Thank you for making Valtio, this is one of the libraries I'm enjoying working with the most. Keep up the amazing work! ๐Ÿ™

In a subscription handler, if state is touched (but not changed) it leads to an infinite loop.

Previous discussions: #13
Related test: https://github.com/pmndrs/valtio/blob/master/tests/subscribe.test.tsx#L53

I'm thinking about a similar kind of API like seen in immer:

subscribe(state, draft => {
    // mutate draft here
})

you could check if the draft was modified after the handler execution and determine if (and which) listeners should be called.

(Another thought: a similar technique might be possible in React components with the useProxy hook, allowing the batching of draft updates by calling listeners after rendering components (instead of waiting for the next eventloop tick with Promise.resolve()), but I don't yet see how this could be implemented)

web components and esm

hey hey! i've been looking for a mobx replacement because it's so bloated: and valtio looks really elegant and promising!

unfortunately, i can't consider valtio because my apps are esm-only and use true web components โ€” no react โ€” i'm currently using lit-element

are there any plans to decouple valtio from react, so it can be used in any application, like mobx?

to alleviate the incompatibility, in addition to the react-decoupling work, the esm distribution would need to be modified to include .js extensions on all non-bare-specifier esm imports (so that library can work natively in browsers), eg:

  • import { createMutableSource, useMutableSource } from './useMutableSource'
    ย  ย  becomes
  • import { createMutableSource, useMutableSource } from './useMutableSource.js'

๐Ÿ‘‹ chase

[Question] Updating multiple state object values at once

I came across interesting behaviour today, here is example of a dialog state that I have with some actions

let dialogState = proxy({
  open: false,
  data: undefined,
  direction: 'bottom-top'
})

function showDialog(open, direction, data) {
    dialogState.open = true;
    dialogState.data = data;
    dialogState.direction = direction;
}

Updating dialog state values this way re renders my react component (that subscribes to all 3 values) 3 times, which is relatively intuitive as I am updating my state 3 times. So I refactored my action to update whole state at once

function showDialog(open, direction, data) {
    dialogState = {
      ...dialogState,
      open,
      data,
      direction
    }
}

but this way it doesn't trigger any updates on react component, so I assume I am missing something? Is there a way to update multiple state values at once in order to consolidate re-rendering calls in react components that use useProxy?

Subscribing to portion of state throws error "Please use proxy object"

The README states "You can access state outside of your components and subscribe to changes" and shows how you can subscribe to a portion of state:

// Subscribe to a portion of state
subscribe(state.obj, () => console.log(`state.obj has changed to ${state.obj}`))

However, doing so throws an error, "Please use proxy object".

See a simple example of this here:
https://codesandbox.io/s/valtio-partial-subscribe-error-plkf3?file=/src/App.js

useLocalProxyWithComputed

Since we have now two separate proxy methods, we will need a new useLocalProxyWithComputed method too for local state/snapshot

ESM files deployed to NPM package contain import references to CommonJS sibbling JS files (missing `.module` suffix))

https://unpkg.com/browse/[email protected]/index.module.js

import { snapshot, subscribe, getVersion } from './vanilla';
export * from './vanilla';

...should point to https://unpkg.com/browse/[email protected]/vanilla.module.js
...but instead point to https://unpkg.com/browse/[email protected]/vanilla.js

Same issue with https://unpkg.com/browse/[email protected]/utils.module.js

import { subscribe, snapshot, proxy } from './vanilla';

Possible regression on 0.8.1

Tried using the newly released 0.8.1 version (via ES Modules) today and couldn't import any of Valtio methods.

JSFiddle

// 0.8.0
import { subscribe as subscribeA } from 'https://cdn.skypack.dev/[email protected]'
// 0.8.1
import { subscribe as subscribeB } from 'https://cdn.skypack.dev/[email protected]'

console.log(subscribeA) // โœ”๏ธ works
console.log(subscribeB) // โŒ SyntaxError: import not found: subscribe

Can valtio replace jotai completely?

Honestly valtio feels so simple but great, but I'm not sure if there are any drawbacks by choosing this over jotai or recoil. Is there any need-to-know before I decide to drop other state managers?

Component re-rendered, but it shouldn't be...

I'm noticing an issue where the last component to have been affected by a mutation is rendered again for the next mutation (even if that mutation did not affect the properties used within that component.)

I made a gif showing what is happening:

Screen Recording 2021-02-06 at 3 23 38 PM

Switching from "count" to "ticks" also renders the component for "count." When we press the button again, only the "count" component is rendered. Switching in the other direction, we observe the same effect.

This is the code I'm using:

import React from 'react'
import { proxy, useProxy } from 'valtio'

const state = proxy({
  count: 0,
  nested: { ticks: 0 },
})

function Counter() {
  const snapshot = useProxy(state)
  console.log('render Counter')
  return <h1>{snapshot.count}</h1>
}

function Controls() {
  const incCounter = () => {
    state.count++
  }
  const incTicks = () => {
    state.nested.ticks++
  }
  console.log('render Controls')
  return (
    <div>
      <button onClick={incCounter}>one up count</button>
      <button onClick={incTicks}>one up nested ticks</button>
    </div>
  )
}

function Ticks() {
  const snapshot = useProxy(state)
  console.log('render Ticks')
  return <h1>{snapshot.nested.ticks}</h1>
}

export default function App() {
  return (
    <>
      <Counter />
      <Ticks />
      <Controls />
    </>
  )
}

Possible to wrap snapshot and state into one?

With JSX Lite we hope to support Valtio, but there is one outstanding issue now that I understand the separation between state and snapshots which is that we don't always know at compilation time if a read or write will happen. This is because references can be grabbed and passed to functions as arguments, and the functions may do reads and writes on objects that we don't know at compile time what they trace back to

So if we could just have one object returned that can be read and written to, that would resolve this challenge . For instance this could be done with the help of a couple libraries like on-change and lodash, in theory, roughly like the below

import { useLocaProxy } from 'valtio/utils'
import { useRef } from 'react;
import onChange from 'on-change';
import { set } from 'lodash';

/**
 * @example
 *    const state = useMustableProxy(() => ({ name: 'Steve' }))
 *    return <input value={state.name} onChange={e => state.name = e.target.value }>
 */
const useMutableProxy = <T extends object>(init: T | (() => T)) => {
  const [snapshot, proxy ] = useLocalProxy(init) 
  const ref = useRef();
  if (!ref.current) { 
    ref.current = onChange(snapshot, (path, value) => set(state, path, value));
  }  

  return ref.current;
}

but if Valtio supported this, e.g. as an optional util, that would allow us to support it properly and be easy for those coming from mobx etc (or like the elegance of not having to think of state vs snapshots and just treat the proxy like a plain mutable object)

A util method to add computed to state

Right now it's natural to add a reactive property to a proxified state object, what's missing is a util method for doing the same with computed:

import { addComputed } from 'valtio/utils'
import state from './store/root' 

addComputed(state, { double(snap) { return snap.counter * 2 } })

Is there any right way on how to persist to storage?

Is there any right way on how to persist the state to the storage, and set it back on the initial app render?
Thanks

EDIT:
Sorry, my bad. I thought there is a weird behavior in the library that reset the state after I set my persisted state. Turns out, it is caused by useEffect inside component.

Expose an external subscribe

Can we add a way to expose the subscribe method so that non-React code can watch the state as well. E.g.:

const globalState = create({
  count: 0,
});

setInterval(() => {
  ++globalState.count;
}, 1000);

globalState.subscribe(({ count}) => {
  console.log(`Called when the count changes: ${count}`);
});

Setting a new array doesn't trigger an update

Hi,

I created a codesandbox to demonstrate the issue: https://codesandbox.io/s/valtio-simple-counter-forked-17yos?file=/src/App.js

Given a state like:

const state = proxy({ arr: ["hello"] });

Then accessing the array like so:

const list = useProxy(state.arr);

I would expect the component to rerender when I do something like this:

const handleClick = () => {
    state.arr = ["hello", "world"];
  };

However, it doesn't update.
It does update if I mutate the list like this: state.arr.push("world");.
But assigning a whole new array doesn't cause an update.

Am I just misunderstanding how it's supposed to work or is this a bug?

Thanks!

Doc typo?

const state = create({ post: fetch(url).then((res) => res.json()) })

Should the state object be wrapped in proxy instead of create?

No context at all.

I implemented a similar repo a long time ago, which is very similar to valtio, but the code is different.

https://github.com/yisar/doux

First of all, context is not needed at all, which is very important, because context means pull-based publish subscribe, while Proxy is push-based dependency collection.

How to set value of state when the value was initialized asynchornously

Hey, this is my fav global state manager thanks. Getting state async works great as per docs:

import { get } from 'idb-keyval'

interface AppState {
  accountType: 'free' | 'paid'
  files: File[]
}

const storedAppState = get<AppState>('xx-appstate')

export const appStateProxy = proxy({
  accountType: storedAppState.then(s => s?.accountType),
  files: storedAppState.then(s => s?.files),
})

My intention is just to hydrate the store with values from IndexedDB, but it's unclear to me how to set these values e.g.

// Error since accountType is a Promise
appStateProxy.accountType = 'free'

Thanks for any help

Can proxy be durable to a hot reload?

Hi,

Several of the examples, like Valtio Tic Tac, work fine until you make an edit to the file that makes the call to proxy. The state is lost and everything resets.

Try going to the above link and clicking a couple of times on the board. Then change any of the strings in the game's source. The game reloads and all the state information is lost. Obviously calling proxy again will cause everything to be reset, which is what will happen when hot reloading reloads the file.

I worked around the issue in my own project by just moving the state and call to proxy to a different file. Valtio drag-and-drop

You can drag around some objects and see their state updates. You can modify any of the sources except globalcontext.js and you won't lose state information. But just like in the other example, if you modify globalcontext.js then all your state information is lost.

Regular states produced by useState, useReducer, and createContext/useContext, are all able to retain their state information after a hot reload. Is there a way to do it with Valtio?

Thanks

No way to access resolved data of the async value from state

looking at how async values are implemented it looks like it's not possible to access the resolved data of the async value directly from the state, which could be useful if for example you want to update the value based on the current value.

for non-async values you can do this:

const state = proxy({ count: 0 })
state.count = state.count + 1

but if count is an async value there doesn't seem to be a way of doing the equivalent

I was expecting this to be possible by doing something like this:

const state = proxy({ count: Promise.resolve(0) })
state.count = state.count.then((currentCount) => currentCount + 1)

but it turns out that the value with which the original promise resolves is not returned from the internal promise handler, so the currentCount from above ends up being undefined.

is there a specific reason why the value is not returned from the internal promise handler?

Update:
I opened a PR with a fix, basically just returning the resolved value from the internal promise handler. I don't know how useful of a change this is so please close the PR if this change doesn't make sense

One more change for computeds

the computed properties should accept two signatures: a function if it's just a getter and a object with get/set if it needs the setter.

๐Ÿ’Š [feature request]: valtio should support non-serializable data, too

Valtio needs something that allows users to put non proxied data into the store. Currently it cannot handle foreign classes and objects, since accessors to parents/children end up proxying the entire system because Valtio cannot differentiate and just climbs through the nested graph. This is shaping up to be a bigger concern because right now it can only hold serializable data.

A suggestion would be to introduce some kind of hint that makes it only check reference equality if the users wants it so.

import { proxy, ref } from 'valtio'

const state = proxy({
  status: true,
  city: 'Berlin',
  body: ref(document.body), // < will not be proxied
  audio: ref(new Audio('/hello.mp3')), // < will not be proxied
})

// I guess it needs to be consistent, so overwrites probably need it, too ...
<div onClick={() => state.audio = ref(new Audo('/chime.mp3'))} />

This wouldn't work with SSR or localCache, but at least we have the ability to throw complex foreign state into the store in local scenarios.

`NonPromise` utility type breaks `React.Ref` type

Context

I'm saving inside the proxy a ref of a button.

Description

But when I use useSnapshot, which returns the type infered from the proxy but wrapped with the utility NonPromise, my ref, which type is React.Ref before the utility, becomes something else (I know this is a wrong assumption, and I understand that React.Ref is actually what the utility translates to, but it would be nice if it translated to the same thing so TypeScript understands that it is exactly what he is expecting).

Example

Here's an example of why it's not nice (typing error):

https://codesandbox.io/s/valtio-ref-with-reactref-191rw?file=/src/App.tsx

Workaround

For people with the same problem here's the workaround:

useSnapshot(state) as typeof state;

https://codesandbox.io/s/valtio-ref-with-reactref-workaround-cmbkn?file=/src/App.tsx

How can I "get" current state?

Really enjoying this library! I came across an issue when I need to add extra data to a state (and persist previous one) object i.e.

assetState.textures = { ...assetState.textures, ...sheet.textures };

Doing something like this results in the following error

Uncaught TypeError: Cannot read property 'prototype' of undefined
    at proxy (index.js:46)
    at Object.set (index.js:125)
    at index.js:137
    at Array.forEach (<anonymous>)
    at proxy (index.js:136)
    at Object.set (index.js:125)
    at index.js:137
    at Array.forEach (<anonymous>)
    at proxy (index.js:136)
    at Object.set (index.js:125)

Error points to this line of code

const emptyCopy = Array.isArray(initialObject) ? [] : Object.create(initialObject.constructor.prototype);

I log out assetState prior to that line that throws errors and it seems it is created correctly, I see things like [[Handler]] etc..

Questions about `subscribe` and issue with `valtio/utils` in NextJS

Hi there,
I've tried Valtio and love it so far :) Thanks guys for the amazing work!

I'm still not sure about the patterns so I'm asking here just to be sure about how to use the subscribe() function.
Do you confirm it works for objects but only if you mutate it, and not replace it ?

I manage to make Valtio detect the replacement by using subscribeKey:

However, I get an error regarding the valtio/utils import when using the same code on Next.JS app:

Server Error
SyntaxError: Unexpected token {

Thanks for your help โค๏ธ

compiles fails after upgrade to 0.7.0

node_modules/valtio/utils.d.ts
TypeScript error in node_modules/valtio/utils.d.ts(1,13):
'=' expected. TS1005

1 | import type { NonPromise } from './vanilla';
| ^
2 | /**
3 | * useLocalProxy
4 | *

error Command failed with exit code 1.

Should there be a useSubscribe?

I ran into a problem where I was doubling the number of subscriptions for a state. I naively subscribed like this:

import React from "react";
import { proxy, useProxy, subscribe } from "valtio";

const state = proxy({ counter: 0, });

function BadSubscribeExample({value, onClick=()=>{} }) {
  console.log("rendering",value);
  subscribe(state, () => {
    console.log("State changed", state);
  });
  return (
    <>
      <button onClick={onClick}>
        Counter: {value}
      </button>
      <div>{value}</div>
    </>
  );
}

export default function App() {
  const snapshot = useProxy(state);
  return (
    <div className="App">
        <BadSubscribeExample value={snapshot.counter} onClick={() => ++state.counter}/>
        <BadSubscribeExample value={5}/>
    </div>
  );
}

This adds one subscription per render.

It would round out the library if it came with a useSubscribe hook to avoid the basic mistake I made. I would be happy to write it if you think it would help.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.