- Notifications
You must be signed in to change notification settings - Fork0
🧙 Valtio makes proxy-state simple for React and Vanilla
License
leweyse/valtio
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
npm install valtio makes proxy-state simple
Valtio turns the object you pass it into a self-aware proxy.
import{proxy,useSnapshot}from'valtio'conststate=proxy({count:0,text:'hello'})
You can make changes to it in the same way you would to a normal js-object.
setInterval(()=>{++state.count},1000)
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` changefunctionCounter(){constsnap=useSnapshot(state)return(<div>{snap.count}<buttononClick={()=>++state.count}>+1</button></div>)}
Note for TypeScript users: Return type of useSnapshot can be too strict.
Thesnap variable returned byuseSnapshot is a (deeply) read-only object.Its type hasreadonly 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'{functionuseSnapshot<Textendsobject>(p:T):T}
See#327 for more information.
Note: useSnapshot returns a new proxy for render optimization.
Internally,useSnapshot callssnapshot in valtio/vanilla,and wraps the snapshot object with another proxy to detect property access.This feature is based onproxy-compare.
Two kinds of proxies are used for different purposes:
proxy()fromvaltio/vanillais for mutation tracking or write tracking.createProxy()fromproxy-compareis for usage tracking or read tracking.
Use ofthis is for expert users.
Valtio tries best to handlethis behaviorbut it's hard to understand without familiarity.
conststate=proxy({count:0,inc(){++this.count},})state.inc()// `this` points to `state` and it works fineconstsnap=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 usethis and prefer arrow function.
conststate=proxy({count:0,inc:()=>{++state.count},})
If you are new to this, it's highly recommended to useeslint-plugin-valtio.
You can access state outside of your components and subscribe to changes.
import{subscribe}from'valtio'// Subscribe to all state changesconstunsubscribe=subscribe(state,()=>console.log('state has changed to',state),)// Unsubscribe by calling the resultunsubscribe()
You can also subscribe to a portion of state.
conststate=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, considersubscribeKey in utils.
import{subscribeKey}from'valtio/utils'conststate=proxy({count:0,text:'hello'})subscribeKey(state,'count',(v)=>console.log('state.count has changed to',v),)
There is another utilwatch which might be convenient in some cases.
import{watch}from'valtio/utils'conststate=proxy({count:0})conststop=watch((get)=>{console.log('state has changed to',get(state))// auto-subscribe on use})
Valtio is compatible with React 19use hook. 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.
import{use}from'react'// React 19// import { use } from 'react18-use' // React 18conststate=proxy({post:fetch(url).then((res)=>res.json())})functionPost(){constsnap=useSnapshot(state)return<div>{use(snap.post).title}</div>}functionApp(){return(<Suspensefallback={<span>waiting...</span>}><Post/></Suspense>)}
It still suffers from "de-opt", which preventsuseTransition to work well. To mitigate it, there is a third-party libraryuse-valtio.
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'conststate=proxy({count:0,dom:ref(document.body),})
You can read state in a component without causing re-render.
functionFoo(){const{ count, text}=state// ...
Or, you can have more control with subscribing in useEffect.
functionFoo(){consttotal=useRef(0)useEffect(()=>subscribe(state.arr,()=>{total.current=state.arr.reduce((p,c)=>p+c)}),[])// ...
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.
functionTextBox(){constsnap=useSnapshot(state,{sync:true})return(<inputvalue={snap.text}onChange={(e)=>(state.text=e.target.value)}/>)}
You can useRedux DevTools Extension for plain objects and arrays.
import{devtools}from'valtio/utils'conststate=proxy({count:0,text:'hello'})constunsub=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.
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'conststate=proxy({count:0,text:'hello'})subscribe(state,()=>{console.log('state is mutated')constobj=snapshot(state)// A snapshot is an immutable object})
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'conststate=proxy({count:1})constComponent=()=>{// 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}<buttononClick={()=>++$state.count}>+1</button></div>)}
You can define computed properties with object getters.
conststate=proxy({count:1,getdoubled(){returnthis.count*2},})
Consider it as an advanced usage, because the behavior ofthis is sometimes confusing.
For more information, check outthis guide.
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'conststate=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
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'conststate=proxyMap([['key','value'],['key2','value2'],])state.set('key','value')state.delete('key')state.get('key')// ---> valuestate.forEach((value,key)=>console.log(key,value))// ---> "key", "value", "key2", "value2"
Valtio v2 works with React 18 and up.It only depends onreact and works with anyrenderers such asreact-dom,react-native,react-three-fiber, and so on.
Valtio works on Node.js, Next.js and other frameworks.
Valtio also works without React. Seevanilla.
Valtio is unopinionated about best practices.The community is working on recipes.
About
🧙 Valtio makes proxy-state simple for React and Vanilla
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Languages
- TypeScript96.1%
- CSS2.3%
- JavaScript1.6%