├── .prettierignore
├── .gitignore
├── tests
├── vitest-setup.ts
└── 01_basic.spec.tsx
├── .codesandbox
└── ci.json
├── tsconfig.esm.json
├── examples
├── tsconfig.json
├── 01_counter
│ ├── index.html
│ ├── src
│ │ ├── main.tsx
│ │ └── app.tsx
│ ├── tsconfig.json
│ └── package.json
└── 02_suspense
│ ├── index.html
│ ├── src
│ ├── main.tsx
│ └── app.tsx
│ ├── tsconfig.json
│ └── package.json
├── tsconfig.cjs.json
├── .github
└── workflows
│ ├── ci.yml
│ └── cd.yml
├── CHANGELOG.md
├── tsconfig.json
├── vite.config.ts
├── eslint.config.js
├── LICENSE
├── src
└── index.ts
├── README.md
├── package.json
└── pnpm-lock.yaml
/.prettierignore:
--------------------------------------------------------------------------------
1 | /pnpm-lock.yaml
2 | /dist
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.swp
3 | node_modules
4 | /dist
5 |
--------------------------------------------------------------------------------
/tests/vitest-setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/vitest';
2 |
--------------------------------------------------------------------------------
/.codesandbox/ci.json:
--------------------------------------------------------------------------------
1 | {
2 | "buildCommand": "compile",
3 | "sandboxes": ["new", "react-typescript-react-ts"],
4 | "node": "18"
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "outDir": "./dist"
6 | },
7 | "include": ["src"]
8 | }
9 |
--------------------------------------------------------------------------------
/examples/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "moduleResolution": "bundler"
6 | },
7 | "exclude": []
8 | }
9 |
--------------------------------------------------------------------------------
/tests/01_basic.spec.tsx:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest';
2 | import { useValtio } from 'use-valtio';
3 |
4 | test('should export functions', () => {
5 | expect(useValtio).toBeDefined();
6 | });
7 |
--------------------------------------------------------------------------------
/examples/01_counter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/02_suspense/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "verbatimModuleSyntax": false,
6 | "declaration": true,
7 | "outDir": "./dist/cjs"
8 | },
9 | "include": ["src"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/01_counter/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import { createRoot } from 'react-dom/client';
3 |
4 | import App from './app';
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | );
11 |
--------------------------------------------------------------------------------
/examples/02_suspense/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import { createRoot } from 'react-dom/client';
3 |
4 | import App from './app';
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | );
11 |
--------------------------------------------------------------------------------
/examples/01_counter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "es2018",
5 | "esModuleInterop": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "skipLibCheck": true,
9 | "allowJs": true,
10 | "noUncheckedIndexedAccess": true,
11 | "exactOptionalPropertyTypes": true,
12 | "jsx": "react-jsx"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/02_suspense/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "es2018",
5 | "esModuleInterop": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "skipLibCheck": true,
9 | "allowJs": true,
10 | "noUncheckedIndexedAccess": true,
11 | "exactOptionalPropertyTypes": true,
12 | "jsx": "react-jsx"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | - uses: pnpm/action-setup@v4
13 | - uses: actions/setup-node@v4
14 | with:
15 | node-version: 'lts/*'
16 | cache: 'pnpm'
17 | - run: pnpm install
18 | - run: pnpm test
19 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [Unreleased]
4 |
5 | ## [0.2.0] - 2024-05-29
6 |
7 | ### Changed
8 |
9 | - Module-first setup #8
10 |
11 | ## [0.1.0] - 2024-03-14
12 |
13 | ### Added
14 |
15 | - feat: usage tracking like useSnapshot #7
16 |
17 | ## [0.0.2] - 2023-01-30
18 |
19 | ### Changed
20 |
21 | - fix: support changing path #1
22 |
23 | ## [0.0.1] - 2023-01-23
24 |
25 | ### Added
26 |
27 | - Initial release
28 |
--------------------------------------------------------------------------------
/examples/01_counter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "dependencies": {
7 | "react": "latest",
8 | "react-dom": "latest",
9 | "use-valtio": "latest",
10 | "valtio": "latest"
11 | },
12 | "devDependencies": {
13 | "@types/react": "latest",
14 | "@types/react-dom": "latest",
15 | "typescript": "latest",
16 | "vite": "latest"
17 | },
18 | "scripts": {
19 | "dev": "vite"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/02_suspense/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "dependencies": {
7 | "react": "latest",
8 | "react-dom": "latest",
9 | "use-valtio": "latest",
10 | "valtio": "latest"
11 | },
12 | "devDependencies": {
13 | "@types/react": "latest",
14 | "@types/react-dom": "latest",
15 | "typescript": "latest",
16 | "vite": "latest"
17 | },
18 | "scripts": {
19 | "dev": "vite"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "es2018",
5 | "esModuleInterop": true,
6 | "module": "nodenext",
7 | "skipLibCheck": true,
8 | "allowJs": true,
9 | "verbatimModuleSyntax": true,
10 | "noUncheckedIndexedAccess": true,
11 | "exactOptionalPropertyTypes": true,
12 | "jsx": "react-jsx",
13 | "baseUrl": ".",
14 | "paths": {
15 | "use-valtio": ["./src/index.ts"]
16 | }
17 | },
18 | "exclude": ["dist", "examples"]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/01_counter/src/app.tsx:
--------------------------------------------------------------------------------
1 | import { proxy } from 'valtio/vanilla';
2 | import { useValtio } from 'use-valtio';
3 |
4 | const countState = proxy({ nested: { count: 0 } });
5 | const Counter = () => {
6 | const snap = useValtio(countState);
7 | return (
8 |
9 | {snap.nested.count}{' '}
10 |
13 |
14 | );
15 | };
16 | const App = () => (
17 |
18 |
19 |
20 | );
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: pnpm/action-setup@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: 'lts/*'
17 | registry-url: 'https://registry.npmjs.org'
18 | cache: 'pnpm'
19 | - run: pnpm install
20 | - run: pnpm run compile
21 | - run: npm publish
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { resolve } from 'node:path';
4 | import { defineConfig } from 'vite';
5 | import tsconfigPaths from 'vite-tsconfig-paths';
6 |
7 | const { DIR, PORT = '8080' } = process.env;
8 |
9 | export default defineConfig(({ mode }) => {
10 | if (mode === 'test') {
11 | return {
12 | test: {
13 | environment: 'happy-dom',
14 | setupFiles: ['./tests/vitest-setup.ts'],
15 | },
16 | plugins: [tsconfigPaths()],
17 | };
18 | }
19 | if (!DIR) {
20 | throw new Error('DIR environment variable is required');
21 | }
22 | return {
23 | root: resolve('examples', DIR),
24 | server: { port: Number(PORT) },
25 | plugins: [tsconfigPaths({ root: resolve('.') })],
26 | };
27 | });
28 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import eslint from '@eslint/js';
2 | import tseslint from 'typescript-eslint';
3 | import importPlugin from 'eslint-plugin-import';
4 | import jsxA11y from 'eslint-plugin-jsx-a11y';
5 | import react from 'eslint-plugin-react';
6 | import reactHooks from 'eslint-plugin-react-hooks';
7 |
8 | export default tseslint.config(
9 | { ignores: ['dist/', 'website/'] },
10 | eslint.configs.recommended,
11 | tseslint.configs.recommended,
12 | importPlugin.flatConfigs.recommended,
13 | jsxA11y.flatConfigs.recommended,
14 | react.configs.flat.recommended,
15 | react.configs.flat['jsx-runtime'],
16 | reactHooks.configs.recommended,
17 | {
18 | settings: {
19 | 'import/resolver': { typescript: true },
20 | react: { version: 'detect' },
21 | },
22 | rules: {
23 | '@typescript-eslint/no-unused-vars': [
24 | 'error',
25 | { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
26 | ],
27 | 'react-hooks/react-compiler': 'error',
28 | },
29 | },
30 | );
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Daishi Kato
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useReducer } from 'react';
2 | import { subscribe, snapshot } from 'valtio/vanilla';
3 | import type { Snapshot } from 'valtio/vanilla';
4 | import { createProxy, isChanged } from 'proxy-compare';
5 |
6 | const targetCache = new WeakMap();
7 |
8 | export function useValtio(proxy: State): Snapshot {
9 | // per-proxy & per-hook affected, it's not ideal but memo compatible
10 | // eslint-disable-next-line react-hooks/react-compiler
11 | // eslint-disable-next-line react-hooks/exhaustive-deps
12 | const affected = useMemo(() => new WeakMap