## 1. Project Overview & Quickstart (pmndrs/valtio) ## File: README.md `npm install valtio` makes proxy-state simple [](https://github.com/pmndrs/valtio/actions/workflows/test.yml?query=branch%3Amain) [](https://bundlephobia.com/result?p=valtio) [](https://www.npmjs.com/package/valtio) [](https://www.npmjs.com/package/valtio) [](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} ++state.count}>+1 ) } ``` 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 is compatible with React 19 `use` 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. ```jsx import { use } from 'react' // React 19 // import { use } from 'react18-use' // React 18 const state = proxy({ post: fetch(url).then((res) => res.json()) }) function Post() { const snap = useSnapshot(state) return {use(snap.post).title} } function App() { return ( ) } ``` It still suffers from "de-opt", which prevents `useTransition` to work well. To mitigate it, there is a third-party library [use-valtio](https://github.com/valtiojs/use-valtio). #### 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. When `ref` is passed an existing Valtio proxy, it returns the same proxy and globally marks it as untracked, including in later proxy composition. See the [`ref` API documentation](./docs/api/advanced/ref.mdx) for details. ```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. #### 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} ++$state.count}>+1 ) } ``` #### 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). #### `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 v2 works with React 18 and up. 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. - [How to organize actions](https://github.com/pmndrs/valtio/blob/main/docs/how-tos/how-to-organize-actions.mdx) - [How to persist states](https://github.com/pmndrs/valtio/blob/main/docs/how-tos/how-to-persist-states.mdx) - [How to use with context](https://github.com/pmndrs/valtio/blob/main/docs/how-tos/how-to-use-with-context.mdx) - [How to split and compose states](https://github.com/pmndrs/valtio/blob/main/docs/how-tos/how-to-split-and-compose-states.mdx) --- ## File: docs/how-tos/how-to-avoid-rerenders-manually.mdx --- title: 'How to avoid rerenders manually' --- # How to avoid rerenders manually ## `useSnapshot` optimizes re-renders automatically This is the basic usage. ```jsx const Component = () => { const { count } = useSnapshot(state) // this is reactive return <>{count} } ``` ## Reading state is valid but not recommended for general use cases ```jsx const Component = () => { const { count } = state // this is not reactive return <>{count} } ``` This will not trigger re-render, but it doesn't follow the react rule like with any other global variables. ## Subscribe and set local state conditionally ```jsx const Component = () => { const [count, setCount] = useState(state.count) useEffect( () => subscribe(state, () => { if (state.count % 2 === 0) { // conditionally update local state setCount(state.count) } }), [], ) return <>{count} } ``` This should work mostly. Theoretically, state can be changed before the subscription. A fix would be the following. ```jsx const Component = () => { const [count, setCount] = useState(state.count) useEffect(() => { const callback = () => { if (state.count % 2 === 0) { // conditionally update local state setCount(state.count) } } const unsubscribe = subscribe(state, callback) callback() return unsubscribe }), []) return <>{count} } ``` For some use cases, using [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) could be easier. --- ## File: docs/how-tos/how-to-easily-access-the-state-from-anywhere-in-the-application.mdx --- title: 'How to easily access the state from anywhere in the application' --- # How to easily access the state from anywhere in the application When working with large applications organizing code in separate files and directories is the go-to way and the Valtio **state** is no exception. In some ways you may want to put the state object in its own file. After being separated in its own file we need a way to access it easily from anywhere in our application. ## Access the state using Path Aliases Imagine that the state is put in `/src/state.js` and you are working with a file in `/src/really/deep/nested/file/myfile.js` the importing of the state will be something like this: `import state from '../../../../state';` which can cause too much brain calculation specially if it is used in different places inside the application. A solution to that is using **Path Aliases** which maps path to a simpler string and the import will look like something similar to that throughout the whole application: `import { state } from '@state';` ## Using JS Config and Babel Config 1. Create the file `/src/state` and put the Valtio **state** into it: ```js import { proxy, useSnapshot, subscribe } from 'valtio' const state = proxy({ foos: [], bar: { ... }, boo: false }) export { state, useSnapshot, subscribe } ``` 2. Create the file `/jsconfig.json` (or `/tsconfig.json` if you're using typescript): ```json { "compilerOptions": { "baseUrl": "src", "paths": { "@state/*": ["./state/*"], "@mypath/*": ["./my/deep/path*"], "@anotherpath/*": ["./my/another/deep/path*"] } }, "exclude": ["node_modules"] } ``` 💡   Using TypeScript? Here are some links for reference https://www.totaltypescript.com/tsconfig-cheat-sheet https://github.com/tsconfig/bases https://www.typescriptlang.org/tsconfig/ 3. Add the **Module Resolver** plugin the plugins in your `babel.config.js`: ```js module.exports = { // ... plugins: [ // The other existing plugins [ 'module-resolver', { root: ['./src'], extensions: ['.js', '.jsx', '.json', '.svg', '.png'], alias: { '@state': './src/state', }, }, ], // ... ], } ``` 4. Install the the Babel Plugin Module Resolver: - Using NPM: `npm install babel-plugin-module-resolver` - Using Yarn: `yarn add babel-plugin-module-resolver` 5. Restart the application server That's it you will now be able to do `import {(state, useSnapshot, subscribe)} from '@state';` from anywhere inside your application. ## Using a third party library You can use a third party library to create aliases and achieve the same result. Example of libraries: - [Module Alias](https://www.npmjs.com/package/module-alias) --- ## File: docs/how-tos/how-to-organize-actions.mdx --- title: 'How to organize actions' --- # How to organize actions Valtio is unopinionated about organizing actions. Here's some recipes to show various patterns are possible. ## Action functions defined in module â„šī¸   Note This way is preferred as it is better for code splitting. ```js import { proxy } from 'valtio' export const state = proxy({ count: 0, name: 'foo', }) export const inc = () => { ++state.count } export const setName = (name) => { state.name = name } ``` ## Action object defined in module ```js import { proxy } from 'valtio' export const state = proxy({ count: 0, name: 'foo', }) export const actions = { inc: () => { ++state.count }, setName: (name) => { state.name = name }, } ``` ## Action methods defined in state ```js export const state = proxy({ count: 0, name: 'foo', inc: () => { ++state.count }, setName: (name) => { state.name = name }, }) ``` ## Action methods using `this` ```js export const state = proxy({ count: 0, name: 'foo', inc() { ++this.count }, setName(name) { this.name = name }, }) ``` ## Using class ```js class State { count = 0 name = 'foo' inc() { ++this.count } setName(name) { this.name = name } } export const state = proxy(new State()) ``` --- ## File: docs/how-tos/how-to-persist-states.mdx --- title: 'How to persist states' --- # How to persist states ## persist with localStorage If your state is JSON serializable, it should be pretty straightforward. ```js const state = proxy( JSON.parse(localStorage.getItem('foo')) || { count: 0, text: 'hello', }, ) subscribe(state, () => { localStorage.setItem('foo', JSON.stringify(state)) }) ``` If you have non serializable values, attach them after deserialization and exclude them for serialization. **_[valtio-persist](https://github.com/Noitidart/valtio-persist) is a library that can help with this._** --- ## File: docs/how-tos/how-to-reset-state.mdx --- title: 'How to reset state' --- # How to reset state In some cases, you might want to reset the state in your proxy instance to its initial values. For example, you are storing form values or some other ephemeral UI state that you want to reset. It turns out this is quite simple to do! ```js import { proxy } from 'valtio' import { deepClone } from 'valtio/utils' const initialObj = { text: 'hello', arr: [1, 2, 3], obj: { a: 'b' }, } const state = proxy(deepClone(initialObj)) const reset = () => { const resetObj = deepClone(initialObj) Object.keys(resetObj).forEach((key) => { state[key] = resetObj[key] }) } ``` Note that we're using the `deepClone()` utility function from `valtio/utils` to copy the initial object in _both_ the `reset` function and the `state` proxy. Using deepClone in the proxy function is a new requirement in v2. Valtio no longer clones the initial state by default. If you reuse the object you pass into the proxy function, you may get unexpected results. Alternatively, you can store the object in another object, which make the reset logic easier: ```js const state = proxy({ obj: initialObj }) const reset = () => { state.obj = deepClone(initialObj) } ``` â„šī¸   Note Using `structuredClone()` In 2022, there was a new global function added called `structuredClone` that is widely available in most modern browsers. You can use `structuredClone` in the same way as `deepClone` above, however `deepClone` is preferred as it will be aware of any `ref`s in your state. > Note: deepClone will convert proxyMap and proxySet back to plain objects. If you have an object that has these within its tree, consider using `unstable_deepProxy` instead. --- ## File: docs/how-tos/how-to-split-and-compose-states.mdx --- title: 'How to split and compose states' --- # How to split and compose states ## You can split states Creating a state with nested object. ```js const state = proxy({ obj1: { a: 1 }, obj2: { b: 2 }, }) ``` You can then split the state into pieces. They are both proxies. ```js const obj1State = state.obj1 const ojb2State = state.obj2 ``` ## You can combine states You can create states and then combine them. ```js const obj1State = proxy({ a: 1 }) const obj2State = proxy({ a: 2 }) const state = proxy({ obj1: obj1State, obj2: obj2State, }) ``` This works equivalently to the previous example. ## You can create circular states While there would be less use cases, you could create a circular structure. ```js const state = proxy({ obj: { foo: 3 }, }) state.obj.bar = state.obj // đŸ¤¯ ``` --- ## File: docs/how-tos/how-to-use-with-context.mdx --- title: 'How to use with context' --- # How to use with context To make a valtio state only live in React lifecycle, you can create a state in a ref, and you can pass it with props or context. ## A basic pattern with context ```jsx import { createContext, useContext } from 'react' import { proxy, useSnapshot } from 'valtio' const MyContext = createContext() const MyProvider = ({ children }) => { const state = useRef(proxy({ count: 0 })).current return {children} } const MyCounter = () => { const state = useContext(MyContext) const snap = useSnapshot(state) return ( <> {snap.count} ++state.count}>+1 ) } ``` ## Alternatives If you are not happy with `useRef` usage, consider: - [use-constant](https://www.npmjs.com/package/use-constant) - [bunshi](https://www.bunshi.org/recipes/valtio/) - You can create custom hooks to `useContext` and optionally `useSnapshot` ### Bunshi example https://codesandbox.io/s/77r53c?file=/molecules.ts --- ## File: docs/how-tos/how-valtio-works.mdx --- title: 'How valtio works' --- # How valtio works Ref: https://github.com/pmndrs/valtio/issues/171 This is to describe the high level abstraction of valtio. ## Articles - [How Valtio Proxy State Works (Vanilla Part)](https://blog.axlight.com/posts/how-valtio-proxy-state-works-vanilla-part/) - [How Valtio Proxy State Works (React Part)](https://blog.axlight.com/posts/how-valtio-proxy-state-works-react-part/) ## Examples ### `proxy()` by examples ```js import { proxy, subscribe } from 'valtio' const s1 = proxy({}) subscribe(s1, () => { console.log('s1 is changed!') }) s1.a = 1 // s1 is changed! ++s1.a // s1 is changed! delete s1.a // s1 is changed! s1.b = 2 // s1 is changed! s1.b = 2 // (not changed) s1.obj = {} // s1 is changed! s1.obj.c = 3 // s1 is changed! const s2 = s1.obj subscribe(s2, () => { console.log('s2 is changed!') }) s1.obj.d = 4 // s1 is changed! and s2 is changed! s2.d = 5 // s1 is changed! and s2 is changed! const s3 = proxy({}) subscribe(s3, () => { console.log('s3 is changed!') }) s1.o = s3 s3.p = 'hello' // s1 is changed! and s3 is changed! s2.q = s3 s3.p = 'hi' // s1 is changed! s2 is changed! and s3 is changed! s1.x = s1 s1.a += 1 // s1 is changed! ``` ### `snapshot()` by examples ```js import { proxy, snapshot } from 'valtio' const p = proxy({}) const s1 = snapshot(p) // is {} but not wrapped by a proxy const s2 = snapshot(p) s1 === s2 // is true because p wasn't changed p.a = 1 // mutate the proxy const s3 = snapshot(p) // is { a: 1 } p.a = 1 // mutation bails out and proxy is not updated const s4 = snapshot(p) s3 === s4 // is still true p.a = 2 // mutate it const s5 = snapshot(p) // is { a: 2 } p.a = 1 // mutate it back const s6 = snapshot(p) // creates a new snapshot s3 !== s6 // is true (different snapshots, even though they are deep equal) p.obj = { b: 2 } // attaching a new object, which will be wrapped by a proxy const s7 = snapshot(p) // is { a: 1, obj: { b: 2 } } p.a = 2 // mutating p const s8 = snapshot(p) // is { a: 2, obj: { b: 2 } } s7 !== s8 // is true because a is different s7.obj === s8.obj // is true because obj is not changed ``` ### `useSnapshot()` by examples ```jsx import { proxy, useSnapshot } from 'valtio' const s1 = proxy({ counter: 0, text: 'Good morning from valtio', foo: { boo: 'baz' } }) const MyComponent = () => { // Using destructuring const { text, counter } = useSnapshot(state) // Multilevel destructiong works as well const { text, counter, { foo }} = useSnapshot(state) // Assigning to a snapshot obeject const snap = useSnapshot(state) return (() => {

{ `${foo} - ${text}` }

{/* - or - */}

{ `${snap.foo.bar} = `${snap.text}}

{ s1.text = e.target.value }} /> { counter } s1.counter++}> + s1.counter--}> - }) } ``` ## Unorganized Notes ### two kinds of proxies valtio has two kinds of proxies, for write and read. We intentionally separate them for hooks and concurrent react. `proxy()` creates a proxy object to detect mutation, "proxy for write" `snapshot()` creates an immutable object from the proxy object `useSnapshot()` wraps the snapshot object again with another proxy (with `proxy-compare`) to detect property access, "proxy for read" ### snapshot creation is optimized ```js const state = proxy({ a: { aa: 1 }, b: { bb: 2 } }) const snap1 = snapshot(state) console.log(snap1) // ---> { a: { aa: 1 }, b: { bb: 2 } } ++state.a.aa const snap2 = snapshot(state) console.log(snap2) // ---> { a: { aa: 2 }, b: { bb: 2 } } snap1.b === snap2.b // this is `true`, it doesn't create a new snapshot because no properties are changed. ``` ### Some notes about valtio implementation in deep valtio's proxy has only one goal: create an immutable snapshot object some design principles: 1. snapshot is created on demand 2. changes are tracked only with version number 3. subscription is used for notifying update (version) 4. version number is hidden as implementation detail 5. proxies are basically used only for version and subscription 6. snapshot creation is optimized with version number some notes about the implementation: 1. proxy can be nested (created at the initialization) 2. proxy can have circular structure (globalVersion to detect it) some notes about promise handling: 1. proxy can have a promise but does nothing 2. when creating a snapshot, it will store the resolved value 3. if it's not resolved, a special object will throw a promise/error --- ## File: docs/how-tos/some-gotchas.mdx --- title: 'Some gotchas' --- # Some gotchas ## `useSnapshot(state)` without property access will always trigger re-render Ref: https://github.com/pmndrs/valtio/issues/209#issuecomment-896859395 Suppose we have this state (or store). ```js const state = proxy({ obj: { count: 0, text: 'hello', }, }) ``` If using the snapshot with accessing count, ```js const snap = useSnapshot(state) snap.obj.count ``` it will re-render only if `count` changes. If the property access is obj, ```js const snap = useSnapshot(state) snap.obj ``` then, it will re-render if `obj` changes. This includes `count` changes and `text` changes. Now, we can subscribe to the portion of the state. ```js const snapObj = useSnapshot(state.obj) snapObj ``` This is technically same as the previous one. It doesn't touch the property of `snapObj`, so it will re-render if `obj` changes. In summary, if a snapshot object (nested or not) is not accessed with any properties, it assumes the entire object is accessed, so any change inside the object will trigger re-render. ## Using `React.memo` with object props may result in unexpected behavior (v1 only) âš ī¸ This behavior is fixed in v2. The `snap` variable returned by `useSnapshot(state)` is tracked for render optimization. If you pass the `snap` or some objects in `snap` to a component with `React.memo`, it may not work as expected because `React.memo` can skip touching object properties. Side note: [react-tracked](https://react-tracked.js.org) has a special `memo` exported as a workaround. We have some options:
  1. Do not use `React.memo`.
  2. Do not pass objects to components with `React.memo` (pass primitive values instead).
  3. Pass in the proxy of that element, and then `useSnapshot` on that proxy.
### Example of (b) ```jsx const ChildComponent = React.memo( ({ title, // string or any primitive values are fine. description, // string or any primitive values are fine. // obj, // objects should be avoided. }) => ( {title} - {description} ), ) const ParentComponent = () => { const snap = useSnapshot(state) return ( ) } ``` ### Example of (c) ```jsx const state = proxy({ objects: [ { id: 1, label: 'foo' }, { id: 2, label: 'bar' }, ], }) const ObjectList = React.memo(() => { const stateSnap = useSnapshot(state) return stateSnap.objects.map((object, index) => ( )) }) const Object = React.memo(({ objectProxy }) => { const objectSnap = useSnapshot(objectProxy) return objectSnap.bar }) ``` ## When to use `state` and when to use `snap` in functional components - snap should be used in render function, every other cases state. - callback functions are not in the render body and therefore state must be used. ```javascript const Component = () => { // this is in render body const handleClick = () => { // this is NOT in render body } return button } ``` - deps in useEffect should be used extracting primitive values from snap. For example: `const { num, string, bool } = snap.watchObj`. - changing a state value based on other state values (without involving values like props in a component), should preferably done outside react. ```javascript subscribe(state.subscribeData, async () => { state.results = await load(state.someData) }) ``` ## Controlled inputs may lose caret position without `sync: true` Ref: https://github.com/pmndrs/valtio/issues/270 When using Valtio state with controlled `` elements, you might notice the text caret jumping to the end while typing in the middle of the existing text. This happens because Valtio batches state updates causing React to re-render after the input event. React resets the DOM value and loses the caret position. **Use `{ sync: true }` to update synchronously and preserve the caret:** ```jsx function Input() { const snap = useSnapshot(state, { sync: true }) return ( { state.text = e.target.value }} /> ) } ``` `sync: true` disables batching so React re-renders within the same event loop tick, skipping the DOM update and preserving the caret position. ## Issue with `array` `proxy` The following use case can occur unexpected results on `arr` subscription: ```javascript const byId = {} arr.forEach((item) => { byId[item.id] = item }) arr.splice(0, arr.length) arr.push(newValue()) someUpdateFunc(byId) Object.keys(byId).forEach((key) => arr.push(byId[key])) ``` [Issues](https://github.com/pmndrs/valtio/issues/712) may arise when handling the array proxy reference in the subsequent steps:
  1. Subscribe array proxy
  2. Use the proxy as snapshot
  3. Assign temp variable for updating
  4. Remove proxy from the array
  5. Update temp
  6. Push temp in the original array
**Example issue case:** ```javascript const a = proxy([ { nested: { nested: { test: 'apple', }, }, }, ]) const sa = snapshot(a) // b. // a. subscribe(a, () => { const updated = snapshot(a) console.log('this is updated proxy. test is Banana', a) console.log('however, for the snapshot of a, test is still apple', updated) }) function handle() { const temp = a[0] // c. a.splice(0, 1) // d. temp.nested.nested.test = 'Banana' // e. a.push(temp) // f. console.log(Object.is(temp, a[0])) // this will be true } ``` **To work around this, swap d and e:** ```javascript // ... function handle() { const temp = a[0] temp.nested.nested.test = 'Banana' // Update first remove from array a.splice(0, 1) a.push(temp) } // ... ``` If the workaround is not applied and you are using react with [devtools()](https://valtio.pmnd.rs/docs/api/utils/devtools), the redux devtools will notify a value update, but the snapshot will remain the same within the devtools' subscription. As a result, the devtools will not display any state change. Additionally, this issue involved not only updating devtools, but also triggering `re-render`. ## Issue with imports when using a library other than `react` (i.e. solidjs) Valtio does not have to work within react, however it was built with react in mind. This being the case, the main `valtio` module exports the react modules alongside the vanilla modules for convenience. This means if you are attempting to import from the main `valtio` module or the `valtio/utils` submodule into a non-react project, you may end up with build errors like this: ``` node_modules/.pnpm/valtio@2.1.4/node_modules/valtio/esm/react.mjs (2:18): "useRef" is not exported by "__vite-optional-peer-dep:react:valtio", imported by "node_modules/.pnpm/valtio@2.1.4/node_modules/valtio/esm/react.mjs". ``` This occurs because the main valtio module exports both framework-agnostic and React-specific functionality, causing build tools like Rollup to look for React dependencies even when they're not needed. There is a simple fix for this, however. Instead of importing from the main `valtio` module like this: ```ts import { proxy, snapshot, subscribe } from 'valtio' ``` you can import directly from the framework-agnostic `vanilla` submodule: ```ts import { proxy, snapshot, subsribe } from 'valtio/vanilla' // this also applies for the utils import { proxyMap, deepClone } from 'valtio/vanilla/utils' ``` ## 2. Official Technical Reference & Guides (pmndrs/docs) ## File: README.md [](https://www.chromatic.com/library?appId=696fd126f0e504f96615dec9&branch=main) [](https://www.chromatic.com/library?appId=6977e41687a50b30c4349650&branch=main) [](docs/getting-started/introduction.mdx) [docs/getting-started/introduction.mdx](docs/getting-started/introduction.mdx) # Usage ```sh $ curl -sL https://raw.githubusercontent.com/pmndrs/docs/refs/heads/main/preview.sh | \ MDX="docs" \ ICON="đŸĨ‘" \ DOCKER_IMAGE="ghcr.io/pmndrs/docs:latest" \ sh ``` - you can pass any option from [configuration](docs/getting-started/introduction.mdx#Configuration) - in `DOCKER_IMAGE`, you can specify any `:tag` value from [docs packages](https://github.com/pmndrs/docs/pkgs/container/docs) container registry # Releasing Every push to `main` redeploys [docs.pmnd.rs](https://docs.pmnd.rs) via [ci.yml](.github/workflows/ci.yml) — no [changeset](.changeset/) needed for that. Add one (`pnpm changeset`) only when downstream consumers pinning `pmndrs/docs/.github/workflows/build.yml@v3` or `ghcr.io/pmndrs/docs:v3` should pull the change. It bumps [`package.json`](package.json), tags `vX.Y.Z` + `vX`, and publishes a matching Docker image — so `@v3` resolves to the latest. TL;DR — site-only tweak: skip. Anything consumers see (workflow, build behavior, templates): add one. # Test Visual tests are performed in the cloud, through [chromatic.yml](.github/workflows/chromatic.yml). You can also replay locally: ```sh $ npx playwright test --update-snapshots $ npx chromatic --playwright --project-token $CHROMATIC_PROJECT_TOKEN ``` --- ## File: docs/getting-started/introduction.mdx --- title: pmndrs/docs nav: 0 --- A static MDX documentation generator, with a GitHub [reusable workflow](./github-actions.mdx). It is primarily used for some `pmndrs/*` projects, but will work for anyone. [Those projects](https://github.com/search?q=%22uses%3A+pmndrs%2Fdocs%2F.github%2Fworkflows%2Fbuild.yml%22+language%3AYAML&type=code&l=YAML) are known to be using this generator. ## INSTALL Pre-requisites: - Install [nvm](https://github.com/nvm-sh/nvm), then: ```sh $ nvm install $ nvm use $ node -v # make sure your version satisfies package.json#engines.node ``` nb: if you want this node version to be your default nvm's one: `nvm alias default node` ```sh $ git clone https://github.com/pmndrs/docs.git $ cd docs $ pnpm install ``` ## Configuration > [!IMPORTANT] > > Default value is always: `""` (think *empty*). | var | description | example | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | | `MDX`\* | Path to `*.mdx` folderNB: can be relative or absolute | `docs` or `~/code/myproject/documentation` | | `NEXT_PUBLIC_LIBNAME`\* | Library name | `React Three Fiber` | | `NEXT_PUBLIC_LIBNAME_SHORT` | Library short name | `r3f` | | `NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL` | Text for the ".docs" suffix link inside the header | `docs` | | `NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF` | Href for the ".docs" suffix link inside the header | `https://docs.pmnd.rs` | | `BASE_PATH` | Base path for the final URL | `/react-three-fiber` | | `DIST_DIR` | Path to the output folder ([within project](https://nextjs.org/docs/app/api-reference/next-config-js/distDir#:~:text=should%20not%20leave%20your%20project%20directory)) | `out` or `docs/out/react-three-fiber` | | `OUTPUT` | Set to `export` for static output | `export` | | `HOME_REDIRECT` | Where the home should redirect | `/getting-started/introduction` | | `MDX_BASEURL` | Base URL for inlining relative images | `http://localhost:60141`or `https://github.com/pmndrs/react-three-fiber/raw/master/docs` | | `SOURCECODE_BASEURL` | Base URL for `sourcecode:` code path | `https://github.com/pmndrs/react-three-fiber/tree/main` | | `EDIT_BASEURL` | Base URL for displaying "Edit this page" URLs | `https://github.com/pmndrs/react-three-fiber/edit/master/docs` | | `NEXT_PUBLIC_URL` | Final URL of the published website | `https://pmndrs.github.io/react-three-fiber` | | `ICON` | Emoji or image to use as (fav)icon (path local to `MDX`) | `🇨🇭` or `/icon.png` or `/favicon.ico` | | `LOGO` | Logo src/path (either FQURL or local to `MDX` path) | `/logo.png` or `https://worldvectorlogo.com/r3f.png` | | `GITHUB` | Github URL | `https://github.com/pmndrs/react-three-fiber` | | `DISCORD` | Discord URL | `https://discord.com/channels/740090768164651008/740093168770613279` | | `THEME_PRIMARY` | Primary accent color | `#323e48` | | `THEME_SCHEME` | Theme scheme | `content` or `expressive` or `fidelity` or `monochrome` or `neutral` or `tonalSpot` or `vibrant` | | `THEME_CONTRAST` | Theme contrast -- value between -1 and 1 | `0` or `-1` or `1` or `-.6` | | `THEME_NOTE` | "note" color | `#1f6feb` | | `THEME_TIP` | "tip" color | `#238636` | | `THEME_IMPORTANT` | "important" color | `#8957e5` | | `THEME_WARNING` | "warning" color | `#d29922` | | `THEME_CAUTION` | "caution" color | `#da3633` | | `CONTRIBUTORS_PAT` | GitHub token for contributors API (see: https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators) | `ghp_1234567890` | \* Required `MDX_BASEURL` Given a `advanced/introduction.mdx` file in the `MDX` folder: ```md ``` becomes (for a `MDX_BASEURL=http://localhost:60141` value): ```md ``` `http://localhost:60141` being the `MDX` folder served. > [!TIP] > When deployed on GitHub Pages, `MDX_BASEURL` will typically value something like `https://github.com/pmndrs/uikit/raw/main/docs`, thanks to [`build.yml`](.github/workflows/build.yml) rule. `THEME_*` We implement [m3 design system](https://m3.material.io/styles/color/system/overview), using [react-mcu](https://github.com/abernier/react-mcu). [](https://github.com/abernier/react-mcu) > [!NOTE] > - [Material Color](https://www.youtube.com/playlist?list=PLsoLz-E4Os4WWkrvRuQ7BJuVF-WfOyfWT) for more information > - We currently don't have secondary/tertiary colors (maybe some day). ## Usage ### dev ```sh $ ( trap 'kill -9 0' SIGINT export _PORT=60141 export MDX=docs export NEXT_PUBLIC_LIBNAME="Poimandres" export NEXT_PUBLIC_LIBNAME_SHORT="pmndrs" export NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL="docs" export NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF="https://docs.pmnd.rs" export BASE_PATH= export DIST_DIR= export OUTPUT= export HOME_REDIRECT= export MDX_BASEURL=http://localhost:$_PORT export SOURCECODE_BASEURL="vscode://file$(pwd)" export EDIT_BASEURL="vscode://file$(pwd)/docs" export NEXT_PUBLIC_URL= export ICON= export LOGO=gutenberg.jpg export GITHUB=https://github.com/pmndrs/docs export DISCORD=https://discord.com/channels/740090768164651008/1264328004172255393 export THEME_PRIMARY="#323e48" export THEME_SCHEME="tonalSpot" export THEME_CONTRAST="0" export THEME_NOTE="#1f6feb" export THEME_TIP="#238636" export THEME_IMPORTANT="#8957e5" export THEME_WARNING="#d29922" export THEME_CAUTION="#da3633" export CONTRIBUTORS_PAT= kill $(lsof -ti:"$_PORT") npx serve $MDX -p $_PORT --no-port-switching --no-clipboard & pnpm run dev & wait ) ``` Then go to: http://localhost:3000 > [!TIP] > If `HOME_REDIRECT=` empty, `/` will not redirect, and instead displays an index of libraries. ### build ```sh $ ( trap 'kill -9 0' SIGINT rm -rf out export _PORT=60141 export MDX=docs export NEXT_PUBLIC_LIBNAME="Poimandres" export NEXT_PUBLIC_LIBNAME_SHORT="pmndrs" export NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL="docs" export NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF="https://docs.pmnd.rs" export BASE_PATH= export DIST_DIR= export OUTPUT=export export HOME_REDIRECT=/getting-started/introduction export MDX_BASEURL=http://localhost:$_PORT export SOURCECODE_BASEURL= export EDIT_BASEURL= export NEXT_PUBLIC_URL= export ICON= export LOGO=gutenberg.jpg export GITHUB=https://github.com/pmndrs/docs export DISCORD=https://discord.com/channels/740090768164651008/1264328004172255393 export THEME_PRIMARY="#323e48" export THEME_SCHEME="tonalSpot" export THEME_CONTRAST="0" export THEME_NOTE="#1f6feb" export THEME_TIP="#238636" export THEME_IMPORTANT="#8957e5" export THEME_WARNING="#d29922" export THEME_CAUTION="#da3633" export CONTRIBUTORS_PAT= pnpm run build kill $(lsof -ti:"$_PORT") npx serve $MDX -p $_PORT --no-port-switching --no-clipboard & npx serve out & wait ) ``` http://localhost:3000 ### Docker ```sh $ docker build -t pmndrs-docs . ``` ``` /* Detailed source-code truncated for AI context efficiency. */ ``` Then go to: http://localhost:3000 ## Agents `llms.txt` dumps and the pmndrs MCP server moved to their own page: [Agents](/agents/introduction).