npm i valtio
makes proxy-state simple
Valtio turns the object you pass it into a self-aware proxy.
import { proxy, useProxy } from 'valtio'
const state = 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, mutate the source. The component will only re-render when the parts of the state you access have changed, it is render-optimized.
function Counter() {
const snapshot = useProxy(state)
return (
<div>
{snapshot.count}
<button onClick={() => ++state.count}>+1</button>
</div>
)
}
You can access state outside of your components and subscribe to changes.
import { subscribe } from 'valtio'
// Suscribe 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.
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 snapshot = useProxy(state)
return <div>{snapshot.post.title}</div>
}
function App() {
return (
<Suspense fallback={<span>waiting...</span>}>
<Post />
</Suspense>
)
}
See pmndrs#62 for more information.
import { proxy, ref } from 'valtio'
const state = proxy({
count: 0,
dom: ref(document.body),
})
You can subscribe a component to state without causing render, just stick the subscribe function into useEffect.
function Foo() {
const ref = useRef(state.obj)
useEffect(() => subscribe(state.obj, () => ref.current = state.obj), [state.obj])
// ...
By default, state mutations are batched before triggering re-render. Sometimes, we want to disable the batching.
function TextBox() {
const snapshot = useProxy(state, { sync: true })
return <input value={snapshot.text} onChange={(e) => (state.text = e.target.value)} />
}
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, 'state name')
Valtio is not tied to React, you can use it in vanilla-js.
import { proxy, subscribe, snapshot } from 'valtio/vanilla'
const state = proxy({ count: 0, text: 'hello' })
subscribe(state, () => {
console.log('state is mutated')
const obj = snapshot(state) // A snapshot is an immutable object
})
You can use it locally in components. Notes
import { useLocalProxy } from 'valtio/utils'
function Foo() {
const [snapshot, state] = useLocalProxy({ count: 0, text: 'hello' })
You can have computed values with dependency tracking. This is for experts. Notes
import { proxyWithComputed } from 'valtio/utils'
const state = proxyWithComputed({
count: 1,
}, {
doubled: snap => snap.count * 2
})
// Computed values accept custom setters too:
const state2 = proxyWithComputed({
firstName: 'Alec',
lastName: 'Baldwin'
}, {
fullName: {
get: (snap) => snap.firstName + ' ' + snap.lastName,
set: (state, newValue) => { [state.firstName, state.lastName] = newValue.split(' ') },
})
// if you want a computed value to derive from another computed, you must declare the dependency first:
const state = proxyWithComputed({
count: 1,
}, {
doubled: snap => snap.count * 2,
quadrupled: snap => snap.doubled * 2
})