Repository: pmndrs/valtio Branch: main Commit: 383ccf185997 Files: 203 Total size: 429.7 KB Directory structure: gitextract_vdkwfl3k/ ├── .codesandbox/ │ └── ci.json ├── .github/ │ ├── DISCUSSION_TEMPLATE/ │ │ └── bug-report.yml │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── config.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── compressed-size.yml │ ├── preview-release.yml │ ├── publish.yml │ ├── test-multiple-builds.yml │ ├── test-multiple-versions.yml │ ├── test-old-typescript.yml │ └── test.yml ├── .gitignore ├── .prettierignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs/ │ ├── api/ │ │ ├── advanced/ │ │ │ ├── ref.mdx │ │ │ ├── snapshot.mdx │ │ │ ├── subscribe-ops.mdx │ │ │ └── subscribe.mdx │ │ ├── basic/ │ │ │ ├── proxy.mdx │ │ │ └── useSnapshot.mdx │ │ ├── hacks/ │ │ │ ├── getVersion.mdx │ │ │ └── internals.mdx │ │ └── utils/ │ │ ├── derive.mdx │ │ ├── devtools.mdx │ │ ├── proxyMap.mdx │ │ ├── proxySet.mdx │ │ ├── proxyWithHistory.mdx │ │ ├── subscribeKey.mdx │ │ ├── unstable_deepProxy.mdx │ │ └── watch.mdx │ ├── guides/ │ │ ├── async.mdx │ │ ├── component-state.mdx │ │ ├── computed-properties.mdx │ │ └── migrating-to-v2.mdx │ ├── how-tos/ │ │ ├── how-to-avoid-rerenders-manually.mdx │ │ ├── how-to-easily-access-the-state-from-anywhere-in-the-application.mdx │ │ ├── how-to-organize-actions.mdx │ │ ├── how-to-persist-states.mdx │ │ ├── how-to-reset-state.mdx │ │ ├── how-to-split-and-compose-states.mdx │ │ ├── how-to-update-values-inside-arrays.mdx │ │ ├── how-to-use-with-context.mdx │ │ ├── how-valtio-works.mdx │ │ └── some-gotchas.mdx │ ├── introduction/ │ │ └── getting-started.mdx │ ├── introduction.mdx │ ├── readme.md │ └── resources/ │ ├── community.mdx │ ├── learn.mdx │ └── libraries.mdx ├── eslint.config.mjs ├── examples/ │ ├── README.md │ ├── counter/ │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ ├── main.tsx │ │ │ ├── prism.css │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ ├── editor-proxyWithHistory/ │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ ├── main.tsx │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ ├── photo-booth-vanillajs/ │ │ ├── index.html │ │ ├── package.json │ │ └── src/ │ │ ├── index.css │ │ └── main.js │ ├── starter/ │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── subscribe/ │ │ └── index.html │ ├── todo/ │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ ├── main.tsx │ │ │ ├── prism.css │ │ │ ├── store.ts │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ └── todo-with-proxyMap/ │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── AddTodoInput.tsx │ │ ├── App.tsx │ │ ├── Filter.tsx │ │ ├── TodoItem.tsx │ │ ├── TodoList.tsx │ │ ├── main.tsx │ │ ├── react-app-env.d.ts │ │ ├── store.ts │ │ └── styles.css │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package.json ├── pnpm-workspace.yaml ├── rollup.config.mjs ├── src/ │ ├── index.ts │ ├── react/ │ │ ├── utils/ │ │ │ └── useProxy.ts │ │ └── utils.ts │ ├── react.ts │ ├── types.d.ts │ ├── utils.ts │ ├── vanilla/ │ │ ├── utils/ │ │ │ ├── deepClone.ts │ │ │ ├── deepProxy.ts │ │ │ ├── devtools.ts │ │ │ ├── proxyMap.ts │ │ │ ├── proxySet.ts │ │ │ ├── subscribeKey.ts │ │ │ └── watch.ts │ │ └── utils.ts │ └── vanilla.ts ├── tests/ │ ├── async.test.tsx │ ├── basic.test.tsx │ ├── class.test.tsx │ ├── deepClone.test.tsx │ ├── deepProxy.test.tsx │ ├── devtools.test.tsx │ ├── getter.test.tsx │ ├── mapset.test.tsx │ ├── memoryleaks.test.ts │ ├── optimization.test.tsx │ ├── performance.test.tsx │ ├── proxyMap.bench.ts │ ├── proxyMap.test.tsx │ ├── proxySet.test.tsx │ ├── ref.test.tsx │ ├── setup.ts │ ├── snapshot.test.ts │ ├── subscribe.test.tsx │ ├── useProxy.test.tsx │ ├── utils.tsx │ └── watch.test.tsx ├── tsconfig.json ├── vitest.config.mts └── website/ ├── .eslintrc.json ├── .gitignore ├── README.md ├── _utils/ │ ├── file_helpers.ts │ └── index.ts ├── components/ │ ├── LandingPage/ │ │ ├── AnimatedShapes.tsx │ │ ├── CodeExample.tsx │ │ ├── GettingStarted.tsx │ │ ├── state.ts │ │ └── useFloatAnimation.tsx │ ├── MDXRenderer/ │ │ ├── MDXRenderer.tsx │ │ └── index.ts │ ├── SEO/ │ │ ├── SEO.tsx │ │ └── index.ts │ ├── ToggleTheme/ │ │ ├── ToggleTheme.tsx │ │ └── index.ts │ └── layouts/ │ ├── BasicLayout/ │ │ ├── BasicLayout.tsx │ │ └── index.ts │ ├── DocLayout/ │ │ ├── DocLayout.tsx │ │ └── index.ts │ ├── Header/ │ │ ├── Header.tsx │ │ └── index.ts │ └── index.ts ├── hooks/ │ ├── index.ts │ ├── useCodesandboxTheme.ts │ ├── useIsomorphicLayoutEffect.ts │ └── useTheme.ts ├── lib/ │ ├── mdx.ts │ └── remarkCodeSandboxURLUpdater.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages/ │ ├── _app.tsx │ ├── _document.tsx │ ├── docs/ │ │ └── [...slug].tsx │ └── index.tsx ├── postcss.config.js ├── state/ │ ├── index.ts │ └── useThemeState.ts ├── styles/ │ ├── landing-page.css │ ├── prism-theme.css │ └── tailwind.css ├── tailwind.config.js ├── tsconfig.json └── types.d.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codesandbox/ci.json ================================================ { "packages": ["dist"], "sandboxes": [ "new", "react-typescript-react-ts", "simple-react-browserify-x9yni", "simple-snowpack-react-o1gmx", "react-parcel-onewf" ], "node": "18" } ================================================ FILE: .github/DISCUSSION_TEMPLATE/bug-report.yml ================================================ labels: ['bug'] body: - type: markdown attributes: value: If you don't have a reproduction link, please choose a different category. - type: textarea attributes: label: Bug Description description: Describe the bug you encountered validations: required: true - type: input attributes: label: Reproduction Link description: A link to a [TypeScript Playground](https://www.typescriptlang.org/play), a [StackBlitz Project](https://stackblitz.com/) or something else with a minimal reproduction. validations: required: true ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [dai-shi] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: ['https://daishi.gumroad.com/l/learn-valtio'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Assigned issue about: This is to create a new issue that already has an assignee. Please open a new discussion otherwise. title: '' labels: '' assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Bug Reports url: https://github.com/pmndrs/valtio/discussions/new?category=bug-report about: Please post bug reports here. - name: Questions url: https://github.com/pmndrs/valtio/discussions/new?category=q-a about: Please post questions here. - name: Other Discussions url: https://github.com/pmndrs/valtio/discussions/new/choose about: Please post ideas and general discussions here. ================================================ FILE: .github/pull_request_template.md ================================================ ## Related Bug Reports or Discussions Fixes # ## Summary ## Check List - [ ] `pnpm run fix` for formatting and linting code and docs ================================================ FILE: .github/workflows/compressed-size.yml ================================================ name: Compressed Size on: [pull_request] jobs: compressed_size: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' cache: 'pnpm' - uses: preactjs/compressed-size-action@49c7ff02f46adc39a83c24e91f6110ba8138a19d # v3 with: pattern: './dist/**/*.{js,mjs}' ================================================ FILE: .github/workflows/preview-release.yml ================================================ name: Preview Release on: [push, pull_request] jobs: preview_release: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' cache: 'pnpm' - run: pnpm install - run: pnpm run build - run: pnpm dlx pkg-pr-new publish './dist' --compact --template './examples/*' ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: release: types: [published] permissions: id-token: write contents: read jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' registry-url: 'https://registry.npmjs.org' cache: 'pnpm' - run: pnpm install - run: pnpm run build - run: npm publish working-directory: dist ================================================ FILE: .github/workflows/test-multiple-builds.yml ================================================ name: Test Multiple Builds on: push: branches: [main] pull_request: types: [opened, synchronize] jobs: test_multiple_builds: runs-on: ubuntu-latest strategy: fail-fast: false matrix: build: [cjs, esm] env: [development] # [development, production] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' cache: 'pnpm' - run: pnpm install - run: pnpm run build - name: Patch for DEV-ONLY if: ${{ matrix.env == 'development' }} run: | sed -i~ "s/it[.a-zA-Z]*('\[DEV-ONLY\]/it('/" tests/*.ts tests/*.tsx sed -i~ "s/it[.a-zA-Z]*('\[PRD-ONLY\]/it.skip('/" tests/*.ts tests/*.tsx - name: Patch for PRD-ONLY if: ${{ matrix.env == 'production' }} run: | sed -i~ "s/it[.a-zA-Z]*('\[PRD-ONLY\]/it('/" tests/*.ts tests/*.tsx sed -i~ "s/it[.a-zA-Z]*('\[DEV-ONLY\]/it.skip('/" tests/*.ts tests/*.tsx - name: Patch for CJS if: ${{ matrix.build == 'cjs' }} run: | sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\1.js')/" vitest.config.mts - name: Patch for ESM if: ${{ matrix.build == 'esm' }} run: | sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/esm\1.mjs')/" vitest.config.mts sed -i~ "1s/^/import.meta.env.MODE='${NODE_ENV}';/" tests/*.ts tests/*.tsx env: NODE_ENV: ${{ matrix.env }} - name: Test ${{ matrix.build }} ${{ matrix.env }} run: | pnpm run test:spec env: NODE_ENV: ${{ matrix.env }} ================================================ FILE: .github/workflows/test-multiple-versions.yml ================================================ name: Test Multiple Versions on: push: branches: [main] pull_request: types: [opened, synchronize] jobs: test_multiple_versions: runs-on: ubuntu-latest strategy: fail-fast: false matrix: react: - 18.0.0 - 18.1.0 - 18.2.0 - 18.3.1 - 19.0.0 - 19.1.0 - 19.2.0 - 19.3.0-canary-e0cc7202-20260227 - 0.0.0-experimental-e0cc7202-20260227 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' cache: 'pnpm' - run: pnpm install - name: Install legacy testing-library if: ${{ startsWith(matrix.react, '16.') || startsWith(matrix.react, '17.') }} run: pnpm add -D @testing-library/react@12.1.4 - name: Patch for React 16 if: ${{ startsWith(matrix.react, '16.') }} run: | sed -i~ '1s/^/import React from "react";/' tests/*.tsx sed -i~ 's/"jsx": "react-jsx"/"jsx": "react"/' tsconfig.json sed -i~ 's/import\.meta\.env[?]\.MODE/"DEVELOPMENT".toLowerCase()/' src/*.ts src/*/*.ts src/*/*/*.ts - name: Test ${{ matrix.react }} run: | pnpm add -D react@${{ matrix.react }} react-dom@${{ matrix.react }} pnpm run test:spec ================================================ FILE: .github/workflows/test-old-typescript.yml ================================================ name: Test Old TypeScript on: push: branches: [main] pull_request: types: [opened, synchronize] jobs: test_old_typescript: runs-on: ubuntu-latest strategy: fail-fast: false matrix: typescript: - 5.9.3 - 5.8.3 - 5.7.3 - 5.6.3 - 5.5.4 - 5.4.5 - 5.3.3 - 5.2.2 - 5.1.6 - 5.0.4 - 4.9.5 - 4.8.4 - 4.7.4 - 4.6.4 - 4.5.5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' cache: 'pnpm' - run: pnpm install - run: pnpm run build - name: Patch for all TS run: | sed -i~ 's/"isolatedDeclarations": true,//' tsconfig.json - name: Patch for v4/v3 TS if: ${{ startsWith(matrix.typescript, '4.') || startsWith(matrix.typescript, '3.') }} run: | sed -i~ 's/"verbatimModuleSyntax": true,//' tsconfig.json - name: Patch for Old TS if: ${{ matrix.typescript == '5.5.4' || matrix.typescript == '5.4.5' || matrix.typescript == '5.3.3' || matrix.typescript == '5.2.2' || matrix.typescript == '5.1.6' || matrix.typescript == '5.0.4' || matrix.typescript == '4.9.5' || matrix.typescript == '4.8.4' || matrix.typescript == '4.7.4' || matrix.typescript == '4.6.4' || matrix.typescript == '4.5.5' }} run: | sed -i~ 's/"moduleResolution": "bundler",/"moduleResolution": "node",/' tsconfig.json sed -i~ 's/"allowImportingTsExtensions": true,//' tsconfig.json sed -i~ 's/"valtio": \["\.\/src\/index\.ts"\],/"valtio": [".\/dist\/index.d.ts"],/' tsconfig.json sed -i~ 's/"valtio\/\*": \["\.\/src\/\*\.ts"\]/"valtio\/*": [".\/dist\/*.d.ts"]/' tsconfig.json sed -i~ 's/"include": .*/"include": ["src\/types.d.ts", "dist\/**\/*", "tests\/**\/*"],/' tsconfig.json - name: Patch for Older TS if: ${{ matrix.typescript == '4.7.4' || matrix.typescript == '4.6.4' || matrix.typescript == '4.5.5' }} run: | pnpm json -I -f package.json -e "this.resolutions={}; this.resolutions['@types/node']='18.13.0';" pnpm add -D @types/node@18.13.0 pnpm add -D vitest@3.2.4 @vitest/coverage-v8@3.2.4 @vitest/ui@3.2.4 - name: Install old TypeScript run: pnpm add -D typescript@${{ matrix.typescript }} - name: Test ${{ matrix.typescript }} run: pnpm run test:types ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: push: branches: [main] pull_request: types: [opened, synchronize] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 'lts/*' cache: 'pnpm' - run: pnpm install - run: pnpm run test:format - run: pnpm run test:types - run: pnpm run test:lint - run: pnpm run test:spec - run: pnpm run build # we don't have any other workflows to test build ================================================ FILE: .gitignore ================================================ # dependencies node_modules .pnp .pnp.js # testing coverage # production dist build # dotenv environment variables file .env .env.local .env.development.local .env.test.local .env.production.local # logs npm-debug.log* yarn-debug.log* yarn-error.log* # misc .DS_Store .idea # examples examples/**/*/package-lock.json examples/**/*/yarn.lock examples/**/*/pnpm-lock.yaml examples/**/*/bun.lockb ================================================ FILE: .prettierignore ================================================ dist pnpm-lock.yaml ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## General Guideline ### Reporting Issues If you have found what you think is a bug, please [start a discussion](https://github.com/pmndrs/valtio/discussions/new?category=bug-report). For any usage questions, please [start a discussion](https://github.com/pmndrs/valtio/discussions/new?category=q-a). ### Suggesting New Features If you are here to suggest a feature, first [start a discussion](https://github.com/pmndrs/valtio/discussions/new?category=ideas) if it does not already exist. From there, we will discuss use-cases for the feature and then finally discuss how it could be implemented. ### Committing We are applying [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/) here. In short, that means a commit has to be one of the following types: Your commit type must be one of the following: - **feat**: A new feature. - **fix**: A bug fix. - **refactor**: A code change that neither fixes a bug nor adds a feature. - **chore**: Changes to the build process, configuration, dependencies, CI/CD pipelines, or other auxiliary tools and libraries. - **docs**: Documentation-only changes. - **test**: Adding missing or correcting existing tests. If you are unfamiliar with the usage of conventional commits, the short version is to simply specify the type as a first word, and follow it with a colon and a space, then start your message from a lowercase letter, like this: ``` feat: add a 'foo' type support ``` You can also specify the scope of the commit in the parentheses after a type: ``` fix(react): change the 'bar' parameter type ``` ### Development If you would like to contribute by fixing an open issue or developing a new feature you can use this suggested workflow: #### General 1. Fork this repository. 2. Create a new feature branch based off the `main` branch. 3. Follow the [Core](#Core) and/or the [Documentation](#Documentation) guide below and come back to this once done. 4. Run `pnpm run fix:format` to format the code. 5. Git stage your required changes and commit (review the commit guidelines below). 6. Submit the PR for review. ##### Core 1. Run `pnpm install` to install dependencies. 2. Create failing tests for your fix or new feature in the [`tests`](./tests/) folder. 3. Implement your changes. 4. Run `pnpm run build` to build the library. _(Pro-tip: `pnpm run build-watch` runs the build in watch mode)_ 5. Run the tests by running `pnpm run test` and ensure that they pass. 6. You can use `pnpm link` to sym-link this package and test it locally on your own project. Alternatively, you may use CodeSandbox CI's canary releases to test the changes in your own project. (requires a PR to be created first) 7. Follow step 4 and onwards from the [General](#General) guide above to bring it to the finish line. ### Pull Requests Please try to keep your pull request focused in scope and avoid including unrelated commits. After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or request improvements, therefore, please check ✅ ["Allow edits from maintainers"](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) on your PR. ## Valtio-specific Guideline ##### Documentation 1. Navigate to the [`website`](./website/) folder. (e.g., `cd website`). 2. Run `pnpm install` to install dependencies in the `website` folder. 3. Run `pnpm run dev` to start the dev server. 4. Navigate to [`http://localhost:3000`](http://localhost:3000) to view the documents. 5. Navigate to the [`docs`](./docs/) folder and make necessary changes to the documents. 6. Add your changes to the documents and see them live reloaded in the browser. 7. Follow step 4 and onwards from the [General](#General) guide above to bring it to the finish line. Thank you for contributing! :heart: ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Poimandres Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ valtio

npm install valtio makes proxy-state simple [![Build Status](https://img.shields.io/github/actions/workflow/status/pmndrs/valtio/test.yml?branch=main&style=flat&colorA=000000&colorB=000000)](https://github.com/pmndrs/valtio/actions/workflows/test.yml?query=branch%3Amain) [![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 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 ( waiting...}> ) } ``` 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. ```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). #### `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/api/advanced/ref.mdx ================================================ --- title: 'ref' section: 'API' subSection: 'Advanced' description: 'Create a ref object' --- # `ref` ## A `ref` allows unproxied state in a Valtio proxy A `ref` is useful in the rare instances you need to nest an object in a `proxy` that is not wrapped in an inner proxy and, therefore, is not tracked. ```js const store = proxy({ users: [ { id: 1, name: 'Juho', uploads: ref([]) }, ] }) }) ``` Once an object is wrapped in a `ref`, it should be mutated without reassigning the object or rewrapping in a new `ref`. ```js // ✅ do mutate store.users[0].uploads.push({ id: 1, name: 'Juho' }) // ✅ do reset store.users[0].uploads.splice(0) // ❌ don't reassign store.users[0].uploads = [] ``` A `ref` should also not be used as the only state in a proxy, making the proxy usage pointless. ## Codesandbox demo https://codesandbox.io/s/valtio-file-load-demo-oo2yzn ================================================ FILE: docs/api/advanced/snapshot.mdx ================================================ --- title: 'snapshot' section: 'API' subSection: 'Advanced' description: 'Create a snapshot of current state' --- # `snapshot` `snapshot` takes a proxy and returns an immutable object, unwrapped from the proxy. Immutability is achieved by _efficiently_ deep copying & freezing the object (see the [Copy on Write](#copy-on-write) section for details). Briefly, in sequential `snapshot` calls, when the values in the proxy have not changed, the previous snapshot's object reference is returned. This allows for shallow comparison in render functions, preventing spurious renders. Snapshots also throw promises, making them work with React Suspense. ```js import { proxy, snapshot } from 'valtio' const store = proxy({ name: 'Mika' }) const snap1 = snapshot(store) // an efficient copy of the current store values, unproxied const snap2 = snapshot(store) console.log(snap1 === snap2) // true, no need to re-render store.name = 'Hanna' const snap3 = snapshot(store) console.log(snap1 === snap3) // false, should re-render ``` ## Copy on Write Even though snapshots are a deep copy of the entire state, they use a lazy copy-on-write mechanism for updates, so in practice they are quick to maintain. For example, if we have a nested object of: ```js const author = proxy({ firstName: 'f', lastName: 'l', books: [{ title: 't1' }, { title: 't2' }], }) const s1 = snapshot(author) ``` The first `snapshot` call creates four new instances: - one for the author, - one for the books array, and - two for the book objects. When we mutate the 2nd book, and take a new `snapshot`: ```js author.books[1].title = 't2b' const s2 = snapshot(author) ``` Then `s2` will have a new copy of the 2nd book, but reuse the existing snapshot of the unchanged 1st book. ```js console.log(s1 === s2) // false console.log(s1.books === s2.books) // false console.log(s1.books[0] === s2.books[0]) // true console.log(s1.books[1] === s2.books[1]) // false ``` Even though this example only reused one of the four existing snapshot instances, it shows that the cost of maintaining snapshots is based on the _depth_ of your state tree (which is typically low, like author to book to book reviews is three levels), and not the _breadth_ (1000s of books). ## Classes Snapshots maintain the original objects' prototypes, so methods and getters work, and correctly evaluate against the snapshot's frozen state. ```js import { proxy, snapshot } from 'valtio' class Author { firstName = 'f' lastName = 'l' fullName() { return `${this.firstName} ${this.lastName}` } } const state = proxy(new Author()) const snap = snapshot(state) // the snapshot has the Author prototype console.log(snap instanceof Author) // true state.firstName = 'f2' // Invocations use the snapshot's state, e.g. this is still 'f' because // inside `fullName`, `this` will be the frozen snapshot instance and not // the mutable state proxy console.log(snap.fullName()) // 'f l' ``` Note that the results of getters and methods are not cached, and are re-evaluated on every call. This should be fine, because the expectation is that they execute very quickly (faster than the overhead of caching them would be worth) and are also deterministic, so the return value is based only on the already-frozen snapshot state. ## Vanilla JavaScript In VanillaJS, `snapshot` is not necessary to access proxied object values, inside or outside of subscribe. However, it is useful, for example, to keep a serializable list of un-proxied objects or check if objects have changed. It also resolves promises.
💡 Tip If you are using valtio outside of react, import from `valtio/vanilla` ```js import { proxy, snapshot } from 'valtio/vanilla' ```
================================================ FILE: docs/api/advanced/subscribe-ops.mdx ================================================ --- title: 'subscribe-ops' section: 'API' subSection: 'Advanced' description: 'Fine-grained mutation tracking with operational transformations (Ops)' --- # Subscribe Ops By default, Valtio's `subscribe` notify you that _something_ has changed in the state proxy. However, you can opt-in to receiving **Ops** (Operations), which are detailed descriptions of exactly what was modified. ## What are Ops? Ops are granular mutation records. When a proxy is updated, Valtio can generate a description of the change as a tuple. ### Op Types - **`set`**: `[op: 'set', path: Path, value: unknown, prevValue: unknown]` - Triggered when a property is assigned a new value. - **`delete`**: `[op: 'delete', path: Path, prevValue: unknown]` - Triggered when a property is deleted. - **`resolve`**: `[op: 'resolve', path: Path, value: unknown]` - Triggered when a promise in the state is fulfilled. - **`reject`**: `[op: 'reject', path: Path, error: unknown]` - Triggered when a promise in the state is rejected. The `Path` is an array of strings or symbols representing the nested location of the property (e.g., `['user', 'profile', 'name']`). ## How to use Ops The "ops" feature is an **opt-in** feature because it introduces a small performance overhead for tracking and allocating these operation objects. ### 1. Enabling Ops You must explicitly enable op-tracking using the unstable API: ```javascript import { unstable_enableOp } from 'valtio' // Enable globally unstable_enableOp(true) ``` ### 2. Receiving Ops in `subscribe` Once enabled, the `subscribe` callback receives an array of these operations as its first argument. ```javascript import { proxy, subscribe, unstable_enableOp } from 'valtio' unstable_enableOp(true) const state = proxy({ count: 0, text: 'hello' }) subscribe(state, (ops) => { ops.forEach((op) => { const [action, path, value, prevValue] = op console.log(`Action: ${action} at ${path.join('.')}`) console.log(`New value:`, value) console.log(`Previous value:`, prevValue) }) }) state.count++ // Output: // Action: set at count // New value: 1 // Previous value: 0 ``` > **Note**: If `unstable_enableOp(true)` is not called, the `ops` argument will be an empty array or `undefined`. ## Use Cases While standard `subscribe` is sufficient for most React UI updates, Ops are useful for specific advanced scenarios: 1. **Network Synchronization**: Instead of sending the entire state over the wire, you can send only the `ops` (patches). This significantly reduces bandwidth consumption in distributed applications. 2. **Undo/Redo History**: Use the `prevValue` provided in `set` and `delete` ops to easily revert state changes. 3. **Audit Logs & Debugging**: Track a sequence of user-driven mutations for analytics or time-travel debugging. 4. **Devtools Integration**: Powering custom development tools that need to visualize state transitions. ## Performance Considerations Enabling Ops has a small overhead cost. For every mutation, Valtio must: - Detect the change type. - Construct the `Path` array. - Allocate the `Op` tuple. In high-frequency update scenarios (e.g., animations, canvas interactions moving hundreds of objects per frame), this can lead to: - Increased garbage collection (GC) pressure due to object allocations. - A measurable drop in frame rates (FPS). **Recommendation**: Only enable `unstable_enableOp` if your application actually consumes the granular `ops` data. ================================================ FILE: docs/api/advanced/subscribe.mdx ================================================ --- title: 'subscribe' section: 'API' subSection: 'Advanced' description: 'Subscribe to a current state/object' --- # `subscribe` ## Subscribe from anywhere You can access state outside your components and subscribe to changes. ```jsx import { proxy, subscribe } from 'valtio' const state = proxy({ count: 0 }) // Subscribe to all changes to the state proxy (and its child proxies) 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') ``` ## Codesandbox demo in VanillaJS https://codesandbox.io/s/valtio-photo-booth-demo-forked-xp8hs?file=/src/main.js ## Advanced: Listening to "Ops" The `subscribe` callback can also receive **Ops** (Operations), which are detailed records of exactly what changed (e.g., which property was set or deleted). This is useful for advanced scenarios like synchronization or undo/redo. Because tracking these operations has a small performance cost, they are disabled by default. For more details on how to enable and use them, check out [Subscribe Ops](./subscribe-ops). ================================================ FILE: docs/api/basic/proxy.mdx ================================================ --- title: 'proxy' section: 'API' subSection: 'Basic' description: 'Create a proxy object.' --- # `proxy` The `proxy` tracks changes to the original object and all nested objects, notifying listeners when an object is modified. ```js import { proxy } 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. ```js setInterval(() => { ++state.count }, 1000) ``` ## Optimizations: noop and batching Updates that set the value of a property to the same value are ignored. Subscribers will not be informed of a version change. ```js const state = proxy({ count: 0 }) state.count = 0 // has no effect ``` Multiple changes in the same event loop tick will be batched together. Subscribers will be notified of a single version change. ```js const state = proxy({ count: 0, text: 'hello' }) // subscribers will be notified once after both mutations state.count = 1 state.text = 'world' ``` ## Nested proxies Proxies can be nested in other `proxy` objects and updated as a whole. ```jsx import { proxy, useSnapshot } from 'valtio' const personState = proxy({ name: 'Timo', role: 'admin' }) const authState = proxy({ status: 'loggedIn', user: personState }) authState.user.name = 'Nina' ``` ## Promises in proxies See [`async`](../../guides/async) for more details. ```jsx import { proxy } from 'valtio' const bombState = proxy({ explosion: new Promise((resolve) => setTimeout(() => resolve('Boom!'), 3000)), }) ``` ## Gotchas If you reassign the proxy to an entirely new object, it will stop working because you are replacing the proxied object with a new object reference. ```jsx let state = proxy({ user: { name: 'Timo' } }) subscribe(state, () => { console.log(state.user.name) }) // will not notify subscribers state = { user: { name: 'Nina' } } // instead let state = proxy({ user: { name: 'Timo' } }) subscribe(state, () => { console.log(state.user.name) // logs "Nina" }) // will notify subscribers state.user.name = 'Nina' ``` Not everything can be proxied. Generally, you are safe if it is serializable. Classes can also be proxied. But avoid special objects. ```jsx // these won't work - changes to these objects won't cause updates // to store state that is unproxied see the docs on ref const state = proxy({ chart: d3.select('#chart'), component: React.createElement('div'), map: new Map(), // see proxyMap storage: localStorage, }) // this will work class User { first = null last = null constructor(first, last) { this.first = first this.last = last } greet() { return `Hi ${this.first}!` } get fullName() { return `${this.first} ${this.last}` } } const state = proxy(new User('Timo', 'Kivinen')) ``` ================================================ FILE: docs/api/basic/useSnapshot.mdx ================================================ --- title: 'useSnapshot' section: 'API' subSection: 'Basic' description: 'Create a local snapshot that catches changes.' --- # `useSnapshot` Create a local `snapshot` that catches changes. Normally, Valtio's snapshots (created via `snapshot()`) are recreated on _any_ change to a proxy, or any of its child proxies. However `useSnapshot` wraps the Valtio snapshot in an access-tracking proxy. This is to make sure your component is render optimized, i.e. it will only re-render if keys that it (or its child components) specifically accessed has changed, and not on every single change to the proxy. ## Usage ### Read from snapshots in render, use the proxy in callbacks Snapshots are read-only to render the JSX from their consistent view of the data. Mutations, and also any reads in callbacks that make mutations, need to be made via the proxy, so that the callback reads & writes the latest value. ```jsx function Counter() { const snap = useSnapshot(state) return (
{snap.count}
) } ``` ### Parent/Child Components If you have a parent component use `useSnapshot`, it can pass snapshots to child components and the parent & children will re-render when the snapshot changes. For example: ```jsx const state = proxy({ books: [ { id: 1, title: 'b1' }, { id: 2, title: 'b2' }, ], }) function AuthorView() { const snap = useSnapshot(state) return (
{snap.books.map((book) => ( ))}
) } function BookView({ book }) { // book is a snapshot return
{book.title}
} ``` If book 2's title is changed, a new `snap` is created and the `AuthorView` and `BookView` components will re-render. Note if `BookView` is `React.memo`d, the 1st `BookView` will not re-render, b/c the 1st `Book` snapshot will be the same instance, as only the 2nd `Book` was mutated (the root `Author` snapshot will also be updated since the list of `books` has changed). ### Child Components Making Mutations The above approach works if `BookView` is read-only; if your child component needs to make mutations, then you'll need to pass the proxy: ```jsx function AuthorView() { const snap = useSnapshot(state) return (
{snap.books.map((book, i) => ( ))}
) } function BookView({ book }) { // book is the proxy, so we can re-snap it + mutate it const snap = useSnapshot(book) return
book.updateTitle()}>{snap.title}
} ``` Or you can pass both the snapshot and proxy together, if you don't want to call `useSnapshot` in the child component: ```jsx function AuthorView() { const snap = useSnapshot(state) return (
{snap.books.map((book, i) => ( ))}
) } ``` There should be no performance difference between these two approaches. ### Read only what you need Every object inside your proxy also becomes a proxy (if you don't use `ref()`). So you can also use them to create a local snapshot. ```jsx function ProfileName() { const snap = useSnapshot(state.profile) return
{snap.name}
} ``` ## Gotchas Beware of replacing the child proxy with something else, breaking your `snapshot`. This will replace the reference of the proxy with what you assign it to which removes the proxy's traps. You can see an example below. ```js console.log(state) { profile: { name: 'valtio' } } childState = state.profile console.log(childState) { name: 'valtio' } state.profile.name = 'react' console.log(childState) { name: 'react' } state.profile = { name: 'new name' } console.log(childState) { name: 'react' } console.log(state) { profile: { name: 'new name' } } ``` `useSnapshot()` depends on the original reference of the child proxy so if you replace it with a new one, the component that is subscribed to the old proxy won't receive new updates because it is still subscribed to the old one. In this case, we recommend one of the approaches below. In neither example do you need to worry about re-renders because it is render-optimized. ```jsx const snap = useSnapshot(state) return
{snap.profile.name}
``` ```jsx const { profile } = useSnapshot(state) return
{profile.name}
``` ## Dev Mode Debug Values In dev mode, `useSnapshot` uses React's `useDebugValue` to output a list of fields that were accessed during rendering, i.e. which specific fields will trigger re-render when the tracking proxy changes.
!!   There are two disclaimers to using the debug value 1. Due to the way `useSnapshot` uses a proxy to recorded accesses _after_ `useSnapshot` has returned, the fields listed in `useDebugValue` are technically from the _previous_ render. 2. Object getter and class getter calls are not included in the `useDebugValue` output, but don't worry, they are actually correctly tracked internally and correctly trigger re-renders when changed.


## Codesandbox demo https://codesandbox.io/s/ping-pong-with-valtio-wb25s?file=/src/App.js ================================================ FILE: docs/api/hacks/getVersion.mdx ================================================ --- title: 'getVersion' section: 'API' subSection: 'Hacks' description: '' --- # `getVersion` In Valtio, updates to proxied objects are tracked internally with a version number. Every mutation to a proxy increases a global version number, and assigns the just mutated proxy, and any parent proxies (which automatically subscribe to their child proxies), to the latest version number. This is how `snapshot` knows whether a new snapshot is necessary: has my proxy's version number changed since the last snapshot? Given its importance to valtio's internal behavior, the `getVersion` helper can be used to check if a proxied object has been updated, but this is not typically useful or recommended to use in application code because `snapshot` and `useSnapshot` already handle version tracking internally. ```js getVersion(proxyObject) ``` ================================================ FILE: docs/api/hacks/internals.mdx ================================================ --- title: 'Internals' section: 'API' subSection: 'Hacks' description: '' --- Valtio exposes some internal capabilities. This isn't for app developers. Use it at your own risk. # `unstable_getInternalStates` This function exposes the internal states. Modifying such states may result in wrong behaviors. You need to understand the [source code](https://github.com/pmndrs/valtio/blob/main/src/vanilla.ts) thoroughly to use it. # `unstable_replaceInternalFunction` This function exposes a way to replace some internal functions. It can easily break things. You need to understand the [source code](https://github.com/pmndrs/valtio/blob/main/src/vanilla.ts) thoroughly to use it. ================================================ FILE: docs/api/utils/derive.mdx ================================================ --- title: 'derive' section: 'API' subSection: 'Utils' description: 'create a new proxy derived from others' --- ## `derive` > **⚠️ Deprecated** > > This package is no longer maintained. Please migrate to > [valtio-reactive](https://github.com/valtiojs/valtio-reactive). ## Installation ```bash npm install derive-valtio ``` #### create a new proxy derived from others You can subscribe to some proxies using `get` to create snapshots used to compute new values. ```js import { derive } from 'derive-valtio' // create a base proxy const state = proxy({ count: 1, }) // create a derived proxy const derived = derive({ doubled: (get) => get(state).count * 2, }) // alternatively, attach derived properties to an existing proxy derive( { tripled: (get) => get(state).count * 3, }, { proxy: state, }, ) ``` ## `underive` ## Stop evaluating In some cases you may want to unsubscribe after deriving a proxy. To do so, use the `underive` util. You may also pass keys to indicate which properties you want to unsubscribe. If you specify `delete` option, it will delete the properties and you can attach new derived properties. ```js import { derive, underive } from 'derive-valtio' const state = proxy({ count: 1, }) const derivedState = derive({ doubled: (get) => get(state).count * 2, }) underive(derivedState) ``` ## Usage patterns ### Re-computes when unrelated properties change With `derive`, the computation will occur if any property of the base proxy changes. Example: ```javascript const baseProxy = proxy({ counter1: 0, counter2: 0, counter3: 0, counter4: 0, }) const countersOneAndTwoSelectors = derive({ sum: (get) => get(baseProxy).counter1 + get(baseProxy).counter2, }) ``` In this example if `baseProxy.counter3` or `baseProxy.counter4` are changed, `countersOneAndTwoSelectors` will re-compute all of the keys in it. ### `get` sub-objects due to update of unrelated proxies on parent proxy As noted on this page, re-computation occurs when unrelated properties change. It is possible to use `get` on sub-objects. This has the benefit of not re-computing when properties of the parent object are updated. Example: ```javascript const baseProxy = proxy({ counter1And2: { counter1: 0, counter2: 0, }, counter3: 0, counter4: 0, }) const countersOneAndTwoSelectors = derive({ sum: (get) => get(baseProxy.counter1And2).counter1 + get(baseProxy.counter1And2).counter2, }) ``` Now even if `counter3` and `counter4` are updated, it will not cause re-computation of `countersOneAndTwoSelectors`. ================================================ FILE: docs/api/utils/devtools.mdx ================================================ --- title: 'devtools' section: 'API' subSection: 'Utils' description: 'Use the Redux DevTools extension with Valtio' --- # devtools #### 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 with vanilla JS Valtio is not tied to React, you can use it in vanillaJS. ```jsx 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 }) ``` #### Use it with TypeScript It's recommended to install and import types from `@redux-devtools/extension` to get types correctly. ```ts import type {} from '@redux-devtools/extension' import { devtools } from 'valtio/utils' ``` ================================================ FILE: docs/api/utils/proxyMap.mdx ================================================ --- title: 'proxyMap' section: 'API' subSection: 'Utils' description: '' --- # `proxyMap` ## Reasoning Native `Maps` store their data in internal slots which are not observable. This means that `valtio` cannot track changes to the data inside of a native `Map`. `proxyMap` is a utility that allows you to create a proxy that mimics the behavior of a `Map` while still allowing valtio to track changes to the data. ## When to use `proxyMap` `proxyMap` is useful when you need the flexibility of a `Map` but still want to track changes to the data. It can be useful if you don't know the structure of the data you'll be working with and this data may have non-primitive values as keys (e.g. objects, arrays, etc.). In this case, you can use `proxyMap` to create a proxy that mimics the behavior of a `Map` while still allowing valtio to track changes to the data. If your data can be represented as a simple object, you should use `proxy` with a simple object instead. It is more performant and easier to use. ## Use a js Map with Valtio This utility creates a proxy which mimics the native Map behavior. The API is the same as the Map API. ```js import { proxyMap } from 'valtio/utils' const state = proxyMap() state.size // ---> 0 state.set(1, 'hello') state.size // ---> 1 state.delete(1) state.size // ---> 0 ``` ## Nesting It can be used inside a `proxy` as well. ```js import { proxyMap } from 'valtio/utils' const state = proxy({ count: 1, map: proxyMap(), }) ``` When using an object as a key, you can wrap it with `ref` so it's not proxied. This is useful if you want to preserve the key equality ```js import { proxyMap } from 'valtio/utils' // with ref const key = ref({}) state.set(key, 'hello') state.get(key) //hello // without ref const key = {} state.set(key, 'value') state.get(key) //undefined ``` ## `isProxyMap` If you want to check if an object is a proxyMap, you can use the `isProxyMap` function. ```js import { proxy, ref } from 'valtio' import { proxyMap, isProxyMap } from 'valtio/utils' const state = proxy({ nativeMap: ref(new Map(/*...*/)), proxyMap: proxyMap(), }) isProxyMap(state.nativeMap) // false isProxyMap(state.proxyMap) // true ``` ## Codesandbox demo https://codesandbox.io/s/github/pmndrs/valtio/tree/main/examples/todo-with-proxyMap ================================================ FILE: docs/api/utils/proxySet.mdx ================================================ --- title: 'proxySet' section: 'API' subSection: 'Utils' description: '' --- # `proxySet` ## Reasoning Native `Sets` store their data in internal slots which are not observable. This means that `valtio` cannot track changes to the data inside of a native `Set`. `proxySet` is a utility that allows you to create a proxy that mimics the behavior of a `Set` while still allowing valtio to track changes to the data. ## When to use `proxySet` `proxySet` is useful when you need the functionality of a `Set` but still want to track changes to the data. `proxySet` can be useful if you're wanting to store unique values or if you want to perform mathematical `Set` operations on the data, such as union, intersection, or difference. `proxySet` supports all of the new methods introduced to `Set`: - `intersection` - `union` - `difference` - `symmetricDifference` - `isSubsetOf` - `isSupersetOf` - `isDisjointFrom` You can see a full list of the methods supported by `proxySet` in the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). If your data can be represented as a simple array or object, and you have no need for the additional functionality provided by `proxySet`, you should use `proxy` with a simple array or object instead. It is more performant and easier to use. ## Use a js `Set` with Valtio This utility creates a proxy which mimics the native `Set` behavior. The API is the same as the native `Set` API. ```js import { proxySet } from 'valtio/utils' const state = proxySet([1, 2, 3]) state.add(4) state.delete(1) state.forEach((v) => console.log(v)) // ---> 2,3,4 ``` ## Nesting It can be used inside a `proxy` as well. ```js import { proxySet } from 'valtio/utils' const state = proxy({ count: 1, set: proxySet(), }) ``` ## `isProxySet` If you want to check if an object is a proxyMap, you can use the `isProxySet` function. ```js import { proxy, ref } from 'valtio' import { proxySet, isProxySet } from 'valtio/utils' const state = proxy({ nativeSet: ref(new Set(/*...*/)), proxySet: proxySet(), }) isProxySet(state.nativeSet) // false isProxySet(state.proxySet) // true ``` ================================================ FILE: docs/api/utils/proxyWithHistory.mdx ================================================ --- title: 'proxyWithHistory' section: 'API' subSection: 'Utils' description: '' --- # `proxyWithHistory` ## Keep a history of snapshots This is a utility function to create a proxy with snapshot history. ```js 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 } ``` ## Codesandbox demo https://codesandbox.io/s/valtio-history-example-v0-m353xc?file=/src/App.tsx ================================================ FILE: docs/api/utils/subscribeKey.mdx ================================================ --- title: 'subscribeKey' section: 'API' subSection: 'Utils' description: '' --- # `subscribeKey` To subscribe to a primitive value in proxy state, consider `subscribeKey`. ```js import { subscribeKey } from 'valtio/utils' const state = proxy({ count: 0, text: 'hello' }) subscribeKey(state, 'count', (v) => console.log('state.count has changed to', v), ) ``` ## Codesandbox demo https://codesandbox.io/s/dynamic-ui-with-valtio-9gme46?file=/src/ComplexCounter.tsx ================================================ FILE: docs/api/utils/unstable_deepProxy.mdx ================================================ --- title: 'unstable_deepProxy' section: 'API' subSection: 'Utils' description: '' --- # `unstable_deepProxy` You can use this utility to recursively go through an object and proxy all proxiable objects with the exception of anything marked with `ref()`. `Map`s will become `proxyMap`s and `Set`s will become `proxySet`s. ```js import { unstable_deepProxy as deepProxy, isProxyMap, isProxySet, } from 'valtio/utils' const obj = { mySet: new Set(), myMap: new Map(), sub: { foo: 'bar', }, } const clonedProxy = deepProxy(obj) console.log(isProxyMap(clonedProxy.myMap)) // true console.log(isProxySet(clonedProxy.mySet)) // true ``` ================================================ FILE: docs/api/utils/watch.mdx ================================================ --- title: 'watch' section: 'API' subSection: 'Utils' description: 'watch for changes.' --- # `watch` > **⚠️ Deprecated** > > This util is no longer maintained. Please migrate to > [valtio-reactive](https://github.com/valtiojs/valtio-reactive). ## Subscription via a getter This utility supports subscribing to multiple proxy objects (unlike `subscribe` which listens to only a single proxy). Proxy objects are subscribed with a `get` function passed to the callback. Any changes to the proxy object (or its child proxies) will rerun the callback. Also note the callback will run once immediately when `watch` is called, even if the proxies have not been yet mutated, to establish the initial subscriptions. ```js import { proxy } from 'valtio' import { watch } from 'valtio/utils' const userState = proxy({ user: { name: 'Juuso' } }) const sessionState = proxy({ expired: false }) watch((get) => { // `get` adds `sessionState` to this callback's watched proxies get(sessionState) const expired = sessionState.expired // Or call it inline const name = get(userState).user.name console.log(`${name}'s session is ${expired ? 'expired' : 'valid'}`) }) // 'Juuso's session is valid' sessionState.expired = true // 'Juuso's session is expired' ``` ## Cleanup You may return a cleanup function that runs both: - Before each re-invocation of the callback (i.e. due to a watched proxy changing) - When the `watch` itself is stopped (by calling the cleanup function returned by `watch`) ```js watch((get) => { const expired = get(sessionState).expired const name = get(userState).user.name console.log(`${name}'s session is ${expired ? 'expired' : 'valid'}`) return () => { if (expired) { console.log('Cleaning up') } } }) // Output from 1st immediate invocation of the callbcak // 'Juuso's session is valid' // Changing a dependency will invoke the cleanup callback first, // but the captured `expired` is false, so we only see output from the // 2nd invocation of the callback. sessionState.expired = true // 'Juuso's session is expired' // Changing a dependency will again invoke the cleanup callback, // and `expired` is true now, so we output from both our cleanup // function as well as the 3rd invocation of the callback. setTimeout(() => { userState.user.name = 'Anonymous' }, 200) // 200ms -> 'Anonymous's session is expired' // 'Cleaning up' logged ``` ## Gotchas If you remove the setTimeout in the example above, `'Juuso's session is expired'` will become `'Anonymous's session is expired'` and it will be logged twice. Valtio will batch updates by default. You may pass `{sync: true}` as a second argument to `watch` to disable batching. ## No usage tracking `watch` currently does not implement the usage tracking of `useSnapshot`, so the callback will rerun anytime a watched proxy (or child proxy) has any fields mutated, regardless of the fields accessed within the `callback`'s code. And the return value of `get` is just the proxy itself, not a snapshot. This is because `watch` is a vanilla primitive built on top of `subscribe`. It is potentially possible for `watch`, or a new `watch`-like method, to implement usage tracking in the future, see [this discussion](https://github.com/pmndrs/valtio/discussions/640) if interested. ================================================ FILE: docs/guides/async.mdx ================================================ --- title: 'Async' section: 'Advanced' description: 'Working with promises and Suspense' --- # `async` ## Promises Promises may be values in a proxied object. They will be resolved in calls to `snapshot`. ```jsx // vanillajs example const countDiv: HTMLElement | null = document.getElementById('count') if (countDiv) countDiv.innerText = '0' const store = proxy({ count: new Promise((r) => setTimeout(() => r(1), 1000)), }) subscribe(store, () => { const value = snapshot(store).count if (countDiv && typeof value === 'number') { countDiv.innerText = String(value) store.count = new Promise((r) => setTimeout(() => r(value + 1), 1000)) } }) ``` ## Suspend your React 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). ## Codesandbox Pokemon fetch demo https://codesandbox.io/s/valtio-pokemon-fetch-x1lkbj?file=/src/App.tsx ## Codesandbox auth demo https://codesandbox.io/s/valtio-async-1pstl1?file=/src/App.tsx ================================================ FILE: docs/guides/component-state.mdx ================================================ --- title: 'Component State' section: 'Advanced' description: 'Isolate component state with useRef' --- # Component State To isolate component state for reusability, Valtio must live in the React lifecycle. You can wrap a `proxy` in a ref, passing it with props or 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} ) } ``` ## Codesandbox example https://codesandbox.io/s/valtio-component-ye5tbg?file=/src/App.tsx ## 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 may also create custom hooks wrapping useContext and optionally useSnapshot. ### Bunshi example https://codesandbox.io/s/77r53c?file=/molecules.ts ================================================ FILE: docs/guides/computed-properties.mdx ================================================ --- title: 'Computed Properties' section: 'Advanced' description: 'Use object getters and setters' --- # Computed Properties In Valtio you can use object & class getters and setters to create computed properties.
ℹ️   Note Getters in JavaScript are a more advanced feature of the language, so Valtio recommends using them with caution. That said, if you are a more advanced JavaScript programmer, they should work as you expect; see the "Note about using `this`" section below.


## Simple object getter ```js const state = proxy({ count: 1, get doubled() { return this.count * 2 }, }) console.log(state.doubled) // 2 // Getter calls on the snapshot work as expected const snap = snapshot(state) console.log(snap.doubled) // 2 // When count changes in the state proxy state.count = 10 // Then snapshot's computed property does not change console.log(snap.doubled) // 2 ``` When you call `state.doubled` on the `state` proxy, it is not cached, and will be re-calculated on every call (if you must cache this result, see the section below on `proxy-memoize`). However, when you make a snapshot, calls to `snap.doubled` are effectively cached, because the value of an object getter is copied during the snapshot process.
ℹ️   Note In the current implementation a computed property should only reference \*\*\_sibling\*\*\* properties, otherwise you'll encounter weird bugs. For example:

```js const user = proxy({ name: 'John', // OK - can reference sibling props via `this` get greetingEn() { return 'Hello ' + this.name }, }) ``` ```js const state = proxy({ // could be nested user: { name: 'John', // OK - can reference sibling props via `this` get greetingEn() { return 'Hello ' + this.name }, }, }) ``` ```js const state = proxy({ user: { name: 'John', }, greetings: { // WRONG - `this` points to `state.greetings`. get greetingEn() { return 'Hello ' + this.user.name }, }, }) ``` ```js const user = proxy({ name: 'John', }) const greetings = proxy({ // WRONG - `this` points to `greetings`. get greetingEn() { return 'Hello ' + this.name }, }) ``` A workaround to it is to attach the related object as a property. ```js const user = proxy({ name: 'John', }) const greetings = proxy({ user, // attach the `user` proxy object // OK - can reference user props via `this` get greetingEn() { return 'Hello ' + this.user.name }, }) ``` Another method would be to create a separate proxy and synchronize with `subscribe`. ```js const user = proxy({ name: 'John', }) const greetings = proxy({ greetingEn: 'Hello ' + user.name, }) subscribe(user, () => { greetings.greetingEn = 'Hello ' + user.name }) ``` Or with `watch`. ```js const user = proxy({ name: 'John', }) const greetings = proxy({}) watch((get) => { greetings.greetingEn = 'Hello ' + get(user).name }) ``` ## Object getter and setter Setters are also supported: ```js const state = proxy({ count: 1, get doubled() { return state.count * 2 }, set doubled(newValue) { state.count = newValue / 2 }, }) // Setter calls on the state work as expected state.doubled = 4 console.log(state.count) // 2 // Getter calls on the snapshot work as expected const snap = snapshot(state) console.log(snap.doubled) // 4 // And setter calls on the snapshot fail as expected // compile error: Cannot assign to 'doubled' because it is a read-only property. // runtime error: TypeError: Cannot assign to read only property 'doubled' of object '#' snap.doubled = 2 ``` As with getters, setter calls within `set doubled` (i.e. `this.count = newValue / 2`) are themselves invoked against the `state` proxy, so the new `count` value will be correctly updated (and subscribers/snapshots notified of the new change). If you make a snapshot, all properties become readonly, so `snap.doubled = 2` will be a compile error, and will also fail at runtime because `snapshot` objects are frozen. ## Class getters and setters Class getters and setters work effectively like object getters and setters: ```js class Counter { count = 1 get doubled() { return this.count * 2 } set doubled(newValue) { this.count = newValue / 2 } } const state = proxy(new Counter()) const snap = snapshot(state) // Changing the state works as expected state.doubled = 4 console.log(state.count) // 2 // And the snapshot value doesn't change console.log(snap.doubled) // 2 ``` Similar to object getters, class getters on the `state` proxy are not cached. However, unlike object getters, class getters on the `snapshot` object are not cached, and are re-evaulated on every access to `snap.doubled`. As mentioned in the `snapshot` docs, this should be fine because the expectation is that getters are as cheap to evaluate as they would be to cache. Also unlike object setters on snapshots (which immediately fail at runtime when called), class setters on snapshots will technically start evaluating, but any mutations they do internally (i.e. `this.count = newValue / 2`) will then fail at runtime because `this` will be a snapshot instance and the snapshots are frozen by `Object.freeze`. ## State usage tracking with `proxy-memoize` If you need to cache getter results even for the `state` proxy itself, you can use Valtio's sister project [proxy-memoize](https://github.com/dai-shi/proxy-memoize). `proxy-memoize` uses a similar usage-based tracking approach as Valtio's `snapshot` function, so it will only re-calculate the getter if fields accessed by the getter logic have actually changed. ```js import { memoize } from 'proxy-memoize' const memoizedDoubled = memoize((snap) => snap.count * 2) const state = proxy({ count: 1, text: 'hello', get doubled() { return memoizedDoubled(snapshot(state)) }, }) ``` With this implementation, when `text` property changes (but `count` has not), the memoized function won't be re-executed. ## Note about using `this` You can use `this` inside of getters and setters, however you should be familiar with how JS `this` works: basically that `this` is whatever object you invoked the call against. So if you call `state.doubled`, then `this` will be the `state` proxy. And if you call `snap.doubled`, then `this` will be the snapshot object (except for object getters & setters, where the current value is copied during the snapshot process, so object getters & setters are never invoked on snapshots). Despite this nuance, you should be able to use `this` as you would expect, and things will "just work". ================================================ FILE: docs/guides/migrating-to-v2.mdx ================================================ --- title: 'How to Migrate to v2 from v1' --- # How to Migrate to v2 from v1 ## Changes in v2 React 19 officially introduces the `use` hook to handle promises. Valtio v1 internally handled promises, which is no longer recommended. In Valtio v2, promises are not handled internally, and developers should explicitly use the `use` hook to manage promises. Note: If you are still using React 18, you can use [the `use` hook shim](https://github.com/dai-shi/react18-use). Valtio v2 also introduces two subtle changes in its design choices: First, the behavior of `proxy(obj)` has changed. In v1, it was a pure function and deeply copied `obj`. In v2, it is an impure function and deeply modifies `obj`. Generally, reusing `obj` is not recommended. Unless you reuse `obj`, nothing will break. Second, the behavior of `useSnapshot()` has been altered. Although it is a subtle change, it is less optimized to ensure compatibility with `useMemo` and the upcoming React compiler. The change may lead to extra re-renders in some edge cases, but it might not be noticeable. Other notable changes to keep things updated and fresh include: - Removal of all deprecated features - Requirement of React version 18 and above - Requirement of TypeScript version 4.5 and above - The build target updated to ES2018 ## Migration for breaking changes ### Resolving promises ```js // v1 import { proxy, useSnapshot } from 'valtio' const state = proxy({ data: fetch(...).then((res) => res.json()) }) const Component = () => { const snap = useSnapshot(state) return <>{JSON.stringify(snap.data)} } ``` ```js // v2 import { use } from 'react' import { proxy, useSnapshot } from 'valtio' const state = proxy({ data: fetch(...).then((res) => res.json()) }) const Component = () => { const snap = useSnapshot(state) return <>{JSON.stringify(use(snap.data))} // If `data` is not an object, you can directly embed it in JSX. // return <>{snap.data} } ``` ### Impure `proxy(obj)` If you don't reuse the object you pass to the proxy, nothing will break. ```js import { proxy } from 'valtio' // This works in both v1 and v2 const state = proxy({ count: 1, obj: { text: 'hi' } }) // This works in both v1 and v2 state.obj = { text: 'hello' } ``` That's the recommended way to use `proxy`. For some reason, if you reuse the object, you need to use `deepClone` explicitly in v2 to keep the same behavior as v1. ```js // v1 import { proxy } from 'valtio' const initialObj = { count: 1, obj: { text: 'hi' } } const state = proxy(initialObj) // and do something later with `initialObj` const newObj = { text: 'hello' } state.obj = newObj // and do something later with `newObj` ``` ```js // v2 import { proxy } from 'valtio' import { deepClone } from 'valtio/utils' const initialObj = { count: 1, obj: { text: 'hi' } } const state = proxy(deepClone(initialObj)) // and do something later with `initialObj` const newObj = { text: 'hello' } state.obj = deepClone(newObj) // and do something later with `newObj` ``` ### `useLayoutEffect` server warning If you're using React 18 with SSR, add this conditional to prevent excessive warnings. ```js import { snapshot, useSnapshot as useSnapshotOrig } from 'valtio' const isSSR = typeof window === 'undefined' export const useSnapshot = isSSR ? (p) => snapshot(p) : useSnapshotOrig // render with `useSnapshot` as usual ``` ## Links - https://github.com/pmndrs/valtio/discussions/703 - https://github.com/pmndrs/valtio/pull/810 ================================================ 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-update-values-inside-arrays.mdx ================================================ --- title: 'How to update values inside arrays' --- # How to update values inside arrays Avoid unnecessary re-renders when updating values inside arrays. ## Basic approach (triggers full list re-render) In this example, iterating over the snapshot items causes the entire list to re-render whenever any item changes. ```js import { proxy, useSnapshot } from 'valtio' const state = proxy({ title: 'My Counter list', items: [ { id: 1, count: 0 }, { id: 2, count: 0 }, ], }) function Counter({ item }) { return (
{item.count}
) } function CounterList() { const snap = useSnapshot(state) return (

{snap.title}

{/* Touching snap.items causes the whole list to re-render when any child updates */} {snap.items.map((item) => ( ))}
) } ``` ## Optimized approach (only changed items re-render) Pass the proxy item (not snapshot) to children and let each child subscribe to its own item. Do not access the entire array from the snapshot in the parent component. ```js import { proxy, useSnapshot } from 'valtio' const state = proxy({ title: 'My Counter list', items: [ { id: 1, count: 0 }, { id: 2, count: 0 }, ], }) function Counter({ item }) { const snap = useSnapshot(item) return (
{snap.count}
) } function CounterList() { const snap = useSnapshot(state) return (

{snap.title}

{/* Only the length is accessed from snap, so only updated children re-render */} {Array.from({ length: snap.items.length }, (_, index) => ( {/* Note that we are passing the proxy object (state) instead of the snapshot (snap) to the child component */} ))}
) } ``` ## Why this works Because the proxy (in this case: `state.items[index]`) is a proxy object instead of a snapshot object, accessing it does not track usage. This prevents the parent component from "subscribing" to the item's internal changes, effectively isolating updates to the child component. When you call `snap.items.map()`, you access every item in the array, causing Valtio to re-render whenever _any_ item changes. By only accessing `snap.items.length`, Valtio only re-renders when the array length changes. Using `Array.from({ length: n }, (_, i) => ...)` creates an array by index without touching snapshot items, letting you access the proxy directly. ================================================ 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} ) } ``` ## 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 }
}) } ``` ## 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 } ``` - 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' ``` ================================================ FILE: docs/introduction/getting-started.mdx ================================================ --- title: 'Getting Started' description: 'Get started with valtio.' --- ![Valtio](./logo.svg 'Valtio') # Valtio ### Proxy state made simple. The Valtio API is minimal, flexible, unopinionated and a touch magical. Valtio's proxy turns the object you pass it into a self-aware proxy, allowing fine-grained subscription and reactivity when making state updates. In React, Valtio shines at render optimization. It is compatible with Suspense and React 18. Valtio is also a viable option in vanilla javascript applications. #### Installation ```bash npm install valtio ``` #### The to-do app example #### 1. proxy Let's learn Valtio by coding a simple to-do app in React and Typescript. We'll start by creating some state using [`proxy`](../api/basic/proxy). ```ts import { proxy, useSnapshot } from 'valtio' type Status = 'pending' | 'completed' type Filter = Status | 'all' type Todo = { description: string status: Status id: number } export const store = proxy<{ filter: Filter; todos: Todo[] }>({ filter: 'all', todos: [], }) ``` #### 2. useSnapshot To access the data in this store, we'll use [`useSnapshot`](../api/basic/useSnapshot). The Todos component will rerender when the "todos" or "filter" properties are updated. Any other data we add to the proxy will be ignored. ```tsx const Todos = () => { const snap = useSnapshot(store) return (
    {snap.todos .filter(({ status }) => status === snap.filter || snap.filter === 'all') .map(({ description, status, id }) => { return (
  • {description}
  • ) })}
) } ``` #### 3. actions Finally, we need to create, update, and delete our todos. To do so, we simply mutate properties on the store we created, not the snap. Commonly, these mutations are wrapped up in functions called actions. ```ts const addTodo = (description: string) => { store.todos.push({ description, status: 'pending', id: Date.now(), }) } const removeTodo = (id: number) => { const index = store.todos.findIndex((todo) => todo.id === id) if (index >= 0) { store.todos.splice(index, 1) } } const toggleDone = (id: number, currentStatus: Status) => { const nextStatus = currentStatus === 'pending' ? 'completed' : 'pending' const todo = store.todos.find((todo) => todo.id === id) if (todo) { todo.status = nextStatus } } const setFilter = (filter: Filter) => { store.filter = filter } ``` Finally, we wire up these actions to our inputs and buttons - check the demo below for the full code. ```tsx ``` ## Codesandbox demo https://codesandbox.io/s/valtio-to-do-list-forked-6w9h3z #### Mutating state outside of components In our first to-do app, Valtio enabled mutations without worrying about performance or "breaking" React. `useSnapshot` turned these mutations into immutable snapshots and optimized renders. But we could have easily used React's own state handling. Let's add a bit of complexity to our to-do app to see what else Valtio offers. We will add a "timeLeft" property to a todo. Now each todo will tick down to zero and become "overdue" if not completed in time. ```ts type Todo = { description: string status: Status id: number timeLeft: number } ``` Among other changes, we will add a Countdown component to display the ticking time for each todo. We are using an advanced technique of passing a nested proxy object to [`useSnapshot`](../api/basic/useSnapshot). Alternatively, make this a dumb component by passing the todo's "timeLeft" as a prop. ```tsx import { useSnapshot } from 'valtio' import { formatTimeDelta, calcTimeDelta } from './utils' import { store } from './App' export const Countdown = ({ index }: { index: number }) => { const snap = useSnapshot(store.todos[index]) const delta = calcTimeDelta(snap.timeLeft) const { days, hours, minutes, seconds } = formatTimeDelta(delta) return ( {delta.total < 0 ? '-' : ''} {days} {days ? ':' : ''} {hours}:{minutes}:{seconds} ) } ``` #### Mutate in module scope Instead of managing multiple timers inside of a React component, let's move these updates outside of React altogether by defining a recursive `countdown` function in module scope which will mutate the todos. ```tsx const countdown = (index: number) => { const todo = store.todos[index] // user removed todo case if (!todo) return // todo done of overdue case if (todo.status !== 'pending') { return } // time over if (todo.timeLeft < 1000) { todo.timeLeft = 0 todo.status = 'overdue' return } setTimeout(() => { todo.timeLeft -= 1000 countdown(index) }, 1000) } ``` We can start the recursive countdown from an enhanced `addTodo` action. ```tsx const addTodo = (e: React.SyntheticEvent, reset: VoidFunction) => { e.preventDefault() const target = e.target as typeof e.target & { deadline: { value: Date } description: { value: string } } const deadline = target.deadline.value const description = target.description.value const now = Date.now() store.todos.push({ description, status: 'pending', id: now, timeLeft: new Date(deadline).getTime() - now, }) // clear the form reset() countdown(store.todos.length - 1) } ``` Please see the rest of the changes in the demo below. #### Subscribe in module scope Being able to mutate state outside of components is a huge benefit. We can also [`subscribe`](../api/advanced/subscribe) to state changes in module scope. We will leave it to you to try this out. You might, for instance, persist your todos to local storage as in [this example](https://github.com/pmndrs/valtio/wiki/How-to-persist-states#persist-with-localstorage). ## Codesandbox demo https://codesandbox.io/s/valtio-countdown-to-do-list-xkgmri ================================================ FILE: docs/introduction.mdx ================================================ # Introduction ================================================ FILE: docs/readme.md ================================================ ## How to contribute ### Basic things to know before adding docs - Docs live in `docs/` folder. - Website lives in `website/` folder. - Docs are written in `mdx` format. - Docs filename shouldn't have spaces. - Website would generate title and other metadata from graymatter in the file. - You should be able to render condesandbox inside `mdx` files by simply adding the url for the same - Once you have a doc, you can add it to the sidebar section by adding it to the nav in `getDocsNav` function inside `website/lib/mdx.ts` ================================================ FILE: docs/resources/community.mdx ================================================ --- title: 'Community' description: 'Discussion, wikis, and other community resources' section: 'Resources' --- # Community ## Recipes wiki 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) ## Discussion Forums - [Pomandres collective](https://discord.gg/poimandres) - discord around React-three and related projects like Valtio - [React Fan](https://discord.gg/MrQdmzd) - discord maintained by Daishi Kato ================================================ FILE: docs/resources/learn.mdx ================================================ --- title: 'Learn' description: 'Courses, tutorials, and more' section: 'Resources' --- # Learn - [learn valtio](https://daishi.gumroad.com/l/learn-valtio) - a code walkthrough by the library author - [7GUIs with Valtio](https://stackblitz.com/edit/vitejs-vite-mxacfhmz?file=src%2Fguis%2FCircleDrawer%2Findex.tsx) - learn Valtio by [building 7 GUIs](https://eugenkiss.github.io/7guis/) ================================================ FILE: docs/resources/libraries.mdx ================================================ --- title: 'Libraries' description: 'Libraries related to Valtio' section: 'Resources' --- # Libraries Valtio provides bare necessities for proxy state management which is great for most projects; however, some users wish to extend the library's feature set. This can be done using 3rd-party libraries created by the community.
⚠️   Warning Disclaimer: These libraries may have bugs, limited maintenance, or other limitations and are not officially recommended by pmndrs or the valtio maintainers. This list is to provide a good starting point for someone looking to extend valtio's feature set.

- [electron-valtio](https://github.com/water-a/electron-valtio) - Share state between the Electron main process and various renderer windows via valtio - [eslint-plugin-valtio](https://github.com/pmndrs/eslint-plugin-valtio) - Eslint plugin for valtio - [storybook-valtio-auto-bind](https://github.com/CosPie/storybook-valtio-auto-bind) - Automatically sync your Storybook args bidirectionally with the Valtio store - [sveltio](https://github.com/wobsoriano/sveltio) - State management solution for Svelte using proxies. Powered by valtio. - [swc-plugin-valtio](https://github.com/sosukesuzuki/swc-plugin-valtio) - Valtio useProxy transformer for SWC. - [tauri-plugin-valtio](https://github.com/ferreira-tb/tauri-store/tree/main/packages/plugin-valtio) - Persistent valtio state for Tauri, accessible from both JavaScript and Rust. - [use-valtio](https://github.com/dai-shi/use-valtio) - Another custom hook to use Valtio proxy state - [valtio-element](https://github.com/lxsmnsyc/valtio-element) - Create reactive, declarative custom elements with valtio - [valtio-factory](https://github.com/mfellner/valtio-factory) - Create valtio state using the factory pattern - [valtio-fsm](https://github.com/valtiojs/valtio-fsm) - A a simple and chainable TypeScript-first finite state machine library powered by Valtio's reactivity system. - [valtio-persist](https://github.com/valtiojs/valtio-persist) - Flexible and performant saving of state to disk. - [valtio-auto-persist](https://github.com/valtiojs/valtio-auto-persist) - Experimental fork of valtio-persist that allows storing your state objects without the need for attaching a key to identify it. It uses [structure-id](https://github.com/overthemike/structure-id) behind the scenes. - [valtio-plugin](https://github.com/valtiojs/valtio-plugin) - A brand new lifecycle plugin system that makes it much easier to customize how you use valtio. - [valtio-reactive](https://github.com/valtiojs/valtio-reactive) - Valtio-reactive makes valtio a reactive library. - [valtio-signal](https://github.com/dai-shi/valtio-signal) - Another React binding for Valtio proxy state - [valtio-yjs](https://github.com/dai-shi/valtio-yjs) - Valtio-yjs makes yjs state easy - [valtio-zod](https://github.com/valtiojs/valtio-zod) - Validate your valtio state updates with [Zod](https://zod.dev) ================================================ FILE: eslint.config.mjs ================================================ import eslint from '@eslint/js' import vitest from '@vitest/eslint-plugin' import { defineConfig, globalIgnores } from 'eslint/config' import importPlugin from 'eslint-plugin-import' import jestDom from 'eslint-plugin-jest-dom' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' import testingLibrary from 'eslint-plugin-testing-library' import tseslint from 'typescript-eslint' export default defineConfig( globalIgnores(['dist/', 'examples/', 'website/', 'coverage/']), eslint.configs.recommended, importPlugin.flatConfigs.recommended, tseslint.configs.recommended, react.configs.flat.recommended, react.configs.flat['jsx-runtime'], reactHooks.configs.flat.recommended, { languageOptions: { parserOptions: { project: true, }, }, settings: { react: { version: 'detect', }, 'import/resolver': { typescript: true, }, }, rules: { eqeqeq: 'error', curly: ['warn', 'multi-line', 'consistent'], 'sort-imports': [ 'error', { ignoreDeclarationSort: true, }, ], 'import/no-unresolved': ['error', { commonjs: true, amd: true }], 'import/named': 'off', 'import/namespace': 'off', 'import/no-named-as-default-member': 'off', 'import/no-duplicates': 'error', 'import/extensions': ['error', 'always', { ignorePackages: true }], 'import/order': [ 'error', { alphabetize: { order: 'asc', caseInsensitive: true }, groups: [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', ], 'newlines-between': 'never', pathGroups: [ { pattern: 'react', group: 'builtin', position: 'before', }, ], pathGroupsExcludedImportTypes: ['builtin'], }, ], '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], }, }, { files: ['tests/**/*.{ts,tsx}'], ...testingLibrary.configs['flat/react'], }, { files: ['tests/**/*.{ts,tsx}'], ...jestDom.configs['flat/recommended'], }, { files: ['tests/**/*.{ts,tsx}'], ...vitest.configs.recommended, settings: { vitest: { typecheck: true } }, }, { files: ['tests/**/*.{ts,tsx}'], rules: { 'import/extensions': ['error', 'never'], 'vitest/consistent-test-it': [ 'error', { fn: 'it', withinDescribe: 'it' }, ], }, }, { files: ['src/vanilla/utils/proxyMap.ts', 'src/vanilla/utils/proxySet.ts'], rules: { '@typescript-eslint/no-unused-expressions': 'off', }, }, { files: ['*.config.*'], languageOptions: { parserOptions: { project: null, }, }, }, ) ================================================ FILE: examples/README.md ================================================ # Examples ## Simple examples - [Starter](https://github.com/pmndrs/valtio/tree/main/examples/starter) - [Counter](https://github.com/pmndrs/valtio/tree/main/examples/counter) - [Todo](https://github.com/pmndrs/valtio/tree/main/examples/todo) - [Todo with proxyMap](https://github.com/pmndrs/valtio/tree/main/examples/todo-with-proxyMap) ================================================ FILE: examples/counter/index.html ================================================ Vite + React + TS
================================================ FILE: examples/counter/package.json ================================================ { "name": "counter", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "@types/lowlight": "^0.0.7", "highlight.js": "^11.11.0", "lowlight": "^3.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-lowlight": "^3.0.1", "valtio": "^2.1.1" }, "devDependencies": { "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", "typescript": "^5.5.3", "vite": "^5.4.1" } } ================================================ FILE: examples/counter/src/App.tsx ================================================ import { proxy, useSnapshot } from 'valtio' import Lowlight from 'react-lowlight' import typescript from 'highlight.js/lib/languages/typescript' // You wrap your state const state = proxy<{ number: number nested?: { ticks: number } }>({ number: 0 }) // You can freely mutate it from anywhere you want ... state.nested = { ticks: 0 } setInterval(() => state.nested && state.nested.ticks++, 200) const Figure = () => { const snap = useSnapshot(state) // This component *only* renders when state.number changes ... return
{snap.number}
} const Ticks = () => { const snap = useSnapshot(state) // This component *only* renders when state.nested.ticks changes ... return
{snap?.nested?.ticks} —
} const Controls = () => { // This component simply mutates the state model, just like that ... return (
state.number++} /> state.number--} />
) } const ButtonUp = ({ onClick }: { onClick: () => void }) => ( ) const ButtonDown = ({ onClick }: { onClick: () => void }) => ( ) const code = `import { proxy, useSnapshot } from 'valtio' // You wrap your state const state = proxy({ number: 0 }) // You can freely mutate it from anywhere you want ... state.nested = { ticks: 0 } setInterval(() => state.nested.ticks++, 200) const Figure = () => { const snap = useSnapshot(state) // This component *only* renders when state.number changes ... return
{snap.number}
} const Ticks = () => { const snap = useSnapshot(state) // This component *only* renders when state.nested.ticks changes ... return
{snap.nested.ticks} —
} const Controls = () => { // This component simply mutates the state model, just like that ... return (
state.number++} /> state.number--} />
) } const ButtonUp = ({ onClick }) => ( ) const ButtonDown = ({ onClick }) => ( ) ` Lowlight.registerLanguage('tsx', typescript) export default function App() { return ( <>
) } ================================================ FILE: examples/counter/src/index.css ================================================ @import url('https://rsms.me/inter/inter.css'); html { font-family: 'Inter', sans-serif; } * { box-sizing: border-box; } html, body { width: 100%; height: 100%; margin: 0; padding: 0; } .app { background: white; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; position: relative; width: 100%; height: 100vh; } svg { pointer-events: none; } path { pointer-events: auto; } .ticks { position: absolute; top: 50px; left: 50px; font-size: 12px; font-weight: 200; font-variant-numeric: tabular-nums; } .figure { font-variant-numeric: tabular-nums; position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); font-weight: 800; font-size: 30em; letter-spacing: -20px; pointer-events: none; color: black; -webkit-text-fill-color: white; /* Will override color (regardless of order) */ -webkit-text-stroke-width: 1px; -webkit-text-stroke-color: black; } .logo { position: absolute; right: 150px; bottom: 150px; } .logo > svg { position: absolute; width: 100px; height: 100px; cursor: pointer; } .code { padding: 10%; } pre { font-size: 0.8em; margin-left: -2.5rem !important; margin-right: -2.5rem !important; width: calc(100% + 5rem); padding: 3em !important; border: 1px solid #eee !important; border-radius: 4px; } .src a * { opacity: 0.5; display: inline-block; margin: 10px 5px; } ================================================ FILE: examples/counter/src/main.tsx ================================================ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import './prism.css' import './index.css' createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: examples/counter/src/prism.css ================================================ /** * VS theme by Andrew Lock (https://andrewlock.net) * Inspired by Visual Studio syntax coloring */ code[class*='language-'], pre[class*='language-'] { color: #393a34; font-family: 'Consolas', 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; font-size: 0.9em; line-height: 1.2em; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre > code[class*='language-'] { font-size: 1em; } pre[class*='language-']::-moz-selection, pre[class*='language-'] ::-moz-selection, code[class*='language-']::-moz-selection, code[class*='language-'] ::-moz-selection { background: #c1def1; } pre[class*='language-']::selection, pre[class*='language-'] ::selection, code[class*='language-']::selection, code[class*='language-'] ::selection { background: #c1def1; } /* Code blocks */ pre[class*='language-'] { padding: 1em; margin: 0.5em 0; overflow: auto; border: 1px solid #dddddd; background-color: white; } /* Inline code */ :not(pre) > code[class*='language-'] { padding: 0.2em; padding-top: 1px; padding-bottom: 1px; background: #f8f8f8; border: 1px solid #dddddd; } .token.comment, .token.prolog, .token.doctype, .token.cdata { color: #b2b8c0; font-style: italic; } .token.namespace { opacity: 0.7; } .token.string { color: #67c2c7; } .token.punctuation, .token.operator { color: #393a34 !important; /* no highlight */ } .token.url, .token.symbol, .token.number, .token.boolean, .token.variable, .token.constant, .token.inserted { color: #36acaa; } .token.atrule, .token.keyword, .token.attr-value, .language-autohotkey .token.selector, .language-json .token.boolean, .language-json .token.number, code[class*='language-css'] { color: #9494ab; } .token.function { color: #ff7bab; } .token.deleted, .language-autohotkey .token.tag { color: #9a050f; } .token.selector, .language-autohotkey .token.keyword { color: #00009f; } .token.important, .token.bold { font-weight: bold; } .token.italic { font-style: italic; } .token.class-name, .language-json .token.property { color: #67c2c7; } .token.tag, .token.selector { color: #67c2c7; } .token.attr-name, .token.property, .token.regex, .token.entity { color: #91adbd; } .token.directive.tag .tag { background: #ffff00; color: #393a34; } /* overrides color-values for the Line Numbers plugin * http://prismjs.com/plugins/line-numbers/ */ .line-numbers .line-numbers-rows { border-right-color: #a5a5a5; } .line-numbers-rows > span:before { color: #2b91af; } /* overrides color-values for the Line Highlight plugin * http://prismjs.com/plugins/line-highlight/ */ .line-highlight { background: rgba(193, 222, 241, 0.2); background: -webkit-linear-gradient( left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0) ); background: linear-gradient( to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0) ); } ================================================ FILE: examples/counter/src/vite-env.d.ts ================================================ /// ================================================ FILE: examples/counter/tsconfig.app.json ================================================ { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"] } ================================================ FILE: examples/counter/tsconfig.json ================================================ { "files": [], "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ] } ================================================ FILE: examples/counter/tsconfig.node.json ================================================ { "compilerOptions": { "target": "ES2022", "lib": ["ES2023"], "module": "ESNext", "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["vite.config.ts"] } ================================================ FILE: examples/counter/vite.config.ts ================================================ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], }) ================================================ FILE: examples/editor-proxyWithHistory/index.html ================================================ Vite + React + TS
================================================ FILE: examples/editor-proxyWithHistory/package.json ================================================ { "name": "editor-proxyWithHistory", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "prismjs": "^1.23.0", "react": "^18.3.1", "react-dom": "^18.3.1", "valtio": "^2.1.1", "valtio-history": "^1.0.0" }, "devDependencies": { "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", "typescript": "^5.5.3", "vite": "^5.4.1" } } ================================================ FILE: examples/editor-proxyWithHistory/src/App.tsx ================================================ import React from 'react' import { useSnapshot } from 'valtio' import { proxyWithHistory } from 'valtio-history' const textProxy = proxyWithHistory({ text: 'Add some text to this initial value and then undo/redo', }) const update = (event: React.ChangeEvent) => (textProxy.value.text = event.target.value) export default function App() { const { value, undo, redo, history, canUndo, canRedo, getCurrentChangeDate } = useSnapshot(textProxy) return (

Editor with history

change {history.index + 1} / {history.nodes.length} | {getCurrentChangeDate().toISOString()}