valtio

npm i valtio makes proxy-state simple [![Build Status](https://img.shields.io/github/actions/workflow/status/pmndrs/valtio/lint-and-type.yml?branch=main&style=flat&colorA=000000&colorB=000000)](https://github.com/pmndrs/valtio/actions?query=workflow%3ALint) [![Build Size](https://img.shields.io/bundlephobia/minzip/valtio?label=bundle%20size&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/result?p=valtio) [![Version](https://img.shields.io/npm/v/valtio?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/valtio) [![Downloads](https://img.shields.io/npm/dt/valtio.svg?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/valtio) [![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff)](https://discord.gg/poimandres) #### Wrap your state object Valtio turns the object you pass it into a self-aware proxy. ```jsx 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. ```jsx 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. ```jsx // This will re-render on `state.count` change but not on `state.text` change function Counter() { const snap = useSnapshot(state) return (
{snap.count}
) } ```
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: ```ts declare module 'valtio' { function useSnapshot(p: T): T } ``` See [#327](https://github.com/pmndrs/valtio/issues/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](https://github.com/dai-shi/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. ```js 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. ```js const state = proxy({ count: 0, inc: () => { ++state.count }, }) ```
If you are new to this, it's highly recommended to use [eslint-plugin-valtio](https://github.com/pmndrs/eslint-plugin-valtio). #### Subscribe from anywhere You can access state outside of your components and subscribe to changes. ```jsx 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. ```jsx 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. ```jsx 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. ```jsx 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. ```jsx const state = proxy({ post: fetch(url).then((res) => res.json()) }) function Post() { const snap = useSnapshot(state) return
{snap.post.title}
} function App() { return ( waiting...}> ) } ``` #### 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](https://github.com/pmndrs/valtio/issues/61) and [#178](https://github.com/pmndrs/valtio/issues/178) for more information. ```js 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. ```jsx function Foo() { const { count, text } = state // ... ``` Or, you can have more control with subscribing in useEffect. ```jsx 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 `` [#270](https://github.com/pmndrs/valtio/issues/270). ```jsx function TextBox() { const snap = useSnapshot(state, { sync: true }) return ( (state.text = e.target.value)} /> ) } ``` #### Dev tools You can use [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools) for plain objects and arrays. ```jsx 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. ```jsx 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. ```js 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 (
{$state.count}
) } ``` #### Computed properties You can define computed properties with object getters. ```js 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](./docs/guides/computed-properties.mdx). #### `proxyWithHistory` util This is a utility function to create a proxy with snapshot history. ```js import { proxyWithHistory } from 'valtio/utils' 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 ```js 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 ```js 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](#use-it-vanilla). #### Plugins - [eslint-plugin-valtio](https://github.com/pmndrs/eslint-plugin-valtio) #### Recipes Valtio is unopinionated about best practices. The community is working on recipes on wiki pages. - [How to organize actions](https://github.com/pmndrs/valtio/wiki/How-to-organize-actions) - [How to persist states](https://github.com/pmndrs/valtio/wiki/How-to-persist-states) - [How to use with context](https://github.com/pmndrs/valtio/wiki/How-to-use-with-context) - [How to split and compose states](https://github.com/pmndrs/valtio/wiki/How-to-split-and-compose-states)