From 66837ff5642519fac6ca09b1c4f2bdc4bed0817d Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 12 Apr 2026 12:37:50 -0500 Subject: [PATCH 1/5] feat: introduce more frameworks hooks for other non-react adapters --- .gitignore | 15 + docs/config.json | 433 ++++++++-- docs/framework/angular/quick-start.md | 6 +- .../angular/reference/functions/injectAtom.md | 45 ++ .../reference/functions/injectSelector.md | 58 ++ .../reference/functions/injectSetValue.md | 108 +++ .../reference/functions/injectStore.md | 65 +- .../reference/functions/injectStoreActions.md | 47 ++ .../reference/functions/injectValue.md | 46 ++ .../angular/reference/functions/shallow.md | 32 + docs/framework/angular/reference/index.md | 12 +- .../interfaces/InjectSelectorOptions.md | 70 ++ docs/framework/preact/quick-start.md | 5 +- .../reference/functions/createStoreContext.md | 95 +++ .../preact/reference/functions/shallow.md | 2 +- .../preact/reference/functions/useAtom.md | 49 ++ .../reference/functions/useCreateAtom.md | 104 +++ .../reference/functions/useCreateStore.md | 161 ++++ .../preact/reference/functions/useSelector.md | 59 ++ .../preact/reference/functions/useSetValue.md | 104 +++ .../preact/reference/functions/useStore.md | 44 +- .../reference/functions/useStoreActions.md | 44 + .../preact/reference/functions/useValue.md | 47 ++ docs/framework/preact/reference/index.md | 14 +- .../interfaces/UseSelectorOptions.md | 38 + .../react/reference/functions/useStore.md | 8 +- docs/framework/solid/quick-start.md | 6 +- .../solid/reference/functions/shallow.md | 2 +- .../solid/reference/functions/useAtom.md | 49 ++ .../solid/reference/functions/useSelector.md | 61 ++ .../solid/reference/functions/useSetValue.md | 104 +++ .../solid/reference/functions/useStore.md | 44 +- .../reference/functions/useStoreActions.md | 44 + .../solid/reference/functions/useValue.md | 49 ++ docs/framework/solid/reference/index.md | 11 +- .../interfaces/UseSelectorOptions.md | 38 + docs/framework/svelte/quick-start.md | 6 +- .../svelte/reference/functions/shallow.md | 2 +- .../svelte/reference/functions/useAtom.md | 49 ++ .../svelte/reference/functions/useSelector.md | 65 ++ .../svelte/reference/functions/useSetValue.md | 104 +++ .../svelte/reference/functions/useStore.md | 31 +- .../reference/functions/useStoreActions.md | 44 + .../svelte/reference/functions/useValue.md | 52 ++ docs/framework/svelte/reference/index.md | 11 +- .../interfaces/UseSelectorOptions.md | 38 + docs/framework/vue/quick-start.md | 6 +- .../vue/reference/functions/shallow.md | 2 +- .../vue/reference/functions/useAtom.md | 46 ++ .../vue/reference/functions/useSelector.md | 60 ++ .../vue/reference/functions/useSetValue.md | 104 +++ .../vue/reference/functions/useStore.md | 44 +- .../reference/functions/useStoreActions.md | 44 + .../vue/reference/functions/useValue.md | 48 ++ docs/framework/vue/reference/index.md | 11 +- .../interfaces/UseSelectorOptions.md | 38 + examples/angular/atoms/README.md | 12 + examples/angular/atoms/angular.json | 56 ++ examples/angular/atoms/package.json | 31 + .../angular/atoms/src/app/app.component.ts | 46 ++ examples/angular/atoms/src/index.html | 12 + examples/angular/atoms/src/main.ts | 4 + examples/angular/atoms/tsconfig.app.json | 9 + examples/angular/atoms/tsconfig.json | 27 + examples/angular/atoms/tsconfig.spec.json | 8 + examples/angular/simple/README.md | 29 +- .../simple/src/app/display.component.ts | 4 +- examples/angular/store-actions/README.md | 12 + examples/angular/store-actions/angular.json | 56 ++ examples/angular/store-actions/package.json | 31 + .../store-actions/src/app/app.component.ts | 56 ++ examples/angular/store-actions/src/index.html | 12 + examples/angular/store-actions/src/main.ts | 4 + .../angular/store-actions/tsconfig.app.json | 9 + examples/angular/store-actions/tsconfig.json | 27 + .../angular/store-actions/tsconfig.spec.json | 8 + examples/angular/store-context/README.md | 14 + examples/angular/store-context/angular.json | 56 ++ examples/angular/store-context/package.json | 31 + .../store-context/src/app/app.component.ts | 98 +++ examples/angular/store-context/src/index.html | 12 + examples/angular/store-context/src/main.ts | 4 + .../angular/store-context/tsconfig.app.json | 9 + examples/angular/store-context/tsconfig.json | 27 + .../angular/store-context/tsconfig.spec.json | 8 + examples/angular/stores/README.md | 12 + examples/angular/stores/angular.json | 56 ++ examples/angular/stores/package.json | 31 + .../angular/stores/src/app/app.component.ts | 49 ++ examples/angular/stores/src/index.html | 12 + examples/angular/stores/src/main.ts | 4 + examples/angular/stores/tsconfig.app.json | 9 + examples/angular/stores/tsconfig.json | 27 + examples/angular/stores/tsconfig.spec.json | 8 + examples/preact/atoms/README.md | 13 + examples/preact/atoms/index.html | 12 + examples/preact/atoms/package.json | 26 + examples/preact/atoms/src/index.tsx | 65 ++ examples/preact/atoms/tsconfig.json | 16 + examples/preact/atoms/vite.config.ts | 9 + examples/preact/simple/README.md | 17 +- examples/preact/simple/src/index.tsx | 52 +- examples/preact/simple/tsconfig.json | 3 +- examples/preact/store-actions/README.md | 12 + examples/preact/store-actions/index.html | 12 + examples/preact/store-actions/package.json | 26 + examples/preact/store-actions/src/index.tsx | 81 ++ examples/preact/store-actions/tsconfig.json | 16 + examples/preact/store-actions/vite.config.ts | 9 + examples/preact/store-context/README.md | 17 + examples/preact/store-context/index.html | 12 + examples/preact/store-context/package.json | 26 + examples/preact/store-context/src/index.tsx | 160 ++++ examples/preact/store-context/tsconfig.json | 16 + examples/preact/store-context/vite.config.ts | 9 + examples/preact/stores/README.md | 12 + examples/preact/stores/index.html | 12 + examples/preact/stores/package.json | 26 + examples/preact/stores/src/index.tsx | 79 ++ examples/preact/stores/tsconfig.json | 16 + examples/preact/stores/vite.config.ts | 9 + examples/solid/atoms/README.md | 12 + examples/solid/atoms/index.html | 12 + examples/solid/atoms/package.json | 20 + examples/solid/atoms/src/index.tsx | 61 ++ examples/solid/atoms/tsconfig.json | 21 + examples/solid/atoms/vite.config.ts | 6 + examples/solid/simple/README.md | 30 +- examples/solid/simple/src/index.tsx | 4 +- examples/solid/store-actions/README.md | 12 + examples/solid/store-actions/index.html | 12 + examples/solid/store-actions/package.json | 20 + examples/solid/store-actions/src/index.tsx | 78 ++ examples/solid/store-actions/tsconfig.json | 21 + examples/solid/store-actions/vite.config.ts | 6 + examples/solid/store-context/README.md | 14 + examples/solid/store-context/index.html | 12 + examples/solid/store-context/package.json | 20 + examples/solid/store-context/src/index.tsx | 165 ++++ examples/solid/store-context/tsconfig.json | 21 + examples/solid/store-context/vite.config.ts | 6 + examples/solid/stores/README.md | 12 + examples/solid/stores/index.html | 12 + examples/solid/stores/package.json | 20 + examples/solid/stores/src/index.tsx | 76 ++ examples/solid/stores/tsconfig.json | 21 + examples/solid/stores/vite.config.ts | 6 + examples/svelte/atoms/README.md | 12 + examples/svelte/atoms/index.html | 12 + examples/svelte/atoms/package.json | 24 + examples/svelte/atoms/src/App.svelte | 36 + examples/svelte/atoms/src/main.ts | 8 + examples/svelte/atoms/src/vite-env.d.ts | 2 + examples/svelte/atoms/svelte.config.js | 5 + examples/svelte/atoms/tsconfig.json | 15 + examples/svelte/atoms/tsconfig.node.json | 13 + examples/svelte/atoms/vite.config.ts | 6 + examples/svelte/simple/README.md | 49 +- examples/svelte/simple/src/Display.svelte | 4 +- examples/svelte/store-actions/README.md | 12 + examples/svelte/store-actions/index.html | 12 + examples/svelte/store-actions/package.json | 24 + examples/svelte/store-actions/src/App.svelte | 47 ++ examples/svelte/store-actions/src/main.ts | 8 + .../svelte/store-actions/src/vite-env.d.ts | 2 + .../svelte/store-actions/svelte.config.js | 5 + examples/svelte/store-actions/tsconfig.json | 15 + .../svelte/store-actions/tsconfig.node.json | 13 + examples/svelte/store-actions/vite.config.ts | 6 + examples/svelte/store-context/README.md | 14 + examples/svelte/store-context/index.html | 12 + examples/svelte/store-context/package.json | 24 + examples/svelte/store-context/src/App.svelte | 29 + .../store-context/src/AtomSection.svelte | 27 + .../store-context/src/StoreSection.svelte | 39 + examples/svelte/store-context/src/context.ts | 13 + examples/svelte/store-context/src/main.ts | 8 + .../svelte/store-context/src/vite-env.d.ts | 2 + .../svelte/store-context/svelte.config.js | 5 + examples/svelte/store-context/tsconfig.json | 15 + .../svelte/store-context/tsconfig.node.json | 13 + examples/svelte/store-context/vite.config.ts | 6 + examples/svelte/stores/README.md | 12 + examples/svelte/stores/index.html | 12 + examples/svelte/stores/package.json | 24 + examples/svelte/stores/src/App.svelte | 44 + examples/svelte/stores/src/main.ts | 8 + examples/svelte/stores/src/vite-env.d.ts | 2 + examples/svelte/stores/svelte.config.js | 5 + examples/svelte/stores/tsconfig.json | 15 + examples/svelte/stores/tsconfig.node.json | 13 + examples/svelte/stores/vite.config.ts | 6 + examples/vue/atoms/README.md | 12 + examples/vue/atoms/index.html | 12 + examples/vue/atoms/package.json | 22 + examples/vue/atoms/src/App.vue | 36 + examples/vue/atoms/src/main.ts | 4 + examples/vue/atoms/src/shims-vue.d.ts | 5 + examples/vue/atoms/tsconfig.json | 20 + examples/vue/atoms/vite.config.ts | 6 + examples/vue/simple/README.md | 6 +- examples/vue/simple/src/Display.vue | 4 +- examples/vue/store-actions/README.md | 12 + examples/vue/store-actions/index.html | 12 + examples/vue/store-actions/package.json | 22 + examples/vue/store-actions/src/App.vue | 49 ++ examples/vue/store-actions/src/main.ts | 4 + examples/vue/store-actions/src/shims-vue.d.ts | 5 + examples/vue/store-actions/tsconfig.json | 20 + examples/vue/store-actions/vite.config.ts | 6 + examples/vue/store-context/README.md | 14 + examples/vue/store-context/index.html | 12 + examples/vue/store-context/package.json | 22 + examples/vue/store-context/src/App.vue | 162 ++++ examples/vue/store-context/src/main.ts | 4 + examples/vue/store-context/src/shims-vue.d.ts | 5 + examples/vue/store-context/tsconfig.json | 20 + examples/vue/store-context/vite.config.ts | 6 + examples/vue/stores/README.md | 12 + examples/vue/stores/index.html | 12 + examples/vue/stores/package.json | 22 + examples/vue/stores/src/App.vue | 46 ++ examples/vue/stores/src/main.ts | 4 + examples/vue/stores/src/shims-vue.d.ts | 5 + examples/vue/stores/tsconfig.json | 20 + examples/vue/stores/vite.config.ts | 6 + packages/angular-store/src/index.ts | 280 ++++++- packages/angular-store/tests/index.test.ts | 364 ++++++++- packages/angular-store/tests/test.test-d.ts | 86 +- .../preact-store/src/createStoreContext.tsx | 72 ++ packages/preact-store/src/index.ts | 176 +--- packages/preact-store/src/shallow.ts | 57 ++ packages/preact-store/src/useAtom.ts | 31 + packages/preact-store/src/useCreateAtom.ts | 48 ++ packages/preact-store/src/useCreateStore.ts | 65 ++ packages/preact-store/src/useSelector.ts | 150 ++++ packages/preact-store/src/useSetValue.ts | 41 + packages/preact-store/src/useStore.ts | 22 + packages/preact-store/src/useStoreActions.ts | 20 + packages/preact-store/src/useValue.ts | 31 + packages/preact-store/tests/index.test.tsx | 544 ++++++++++++- packages/preact-store/tests/test.test-d.ts | 196 ++++- packages/react-store/src/useStore.ts | 5 + packages/solid-store/src/index.tsx | 87 +- packages/solid-store/src/shallow.ts | 57 ++ packages/solid-store/src/useAtom.ts | 32 + packages/solid-store/src/useSelector.ts | 57 ++ packages/solid-store/src/useSetValue.ts | 39 + packages/solid-store/src/useStore.ts | 24 + packages/solid-store/src/useStoreActions.ts | 19 + packages/solid-store/src/useValue.ts | 33 + packages/solid-store/tests/index.test.tsx | 300 ++++++- packages/solid-store/tests/test.test-d.ts | 84 +- packages/svelte-store/src/index.svelte.ts | 178 +++- .../svelte-store/tests/BaseStore.test.svelte | 4 +- .../svelte-store/tests/Render.test.svelte | 4 +- packages/svelte-store/tests/Value.test.svelte | 16 + packages/svelte-store/tests/index.test.ts | 28 +- packages/svelte-store/tests/test.test-d.ts | 99 +++ packages/vue-store/src/index.ts | 93 +-- packages/vue-store/src/shallow.ts | 57 ++ packages/vue-store/src/useAtom.ts | 29 + packages/vue-store/src/useSelector.ts | 57 ++ packages/vue-store/src/useSetValue.ts | 39 + packages/vue-store/src/useStore.ts | 23 + packages/vue-store/src/useStoreActions.ts | 19 + packages/vue-store/src/useValue.ts | 32 + packages/vue-store/tests/index.test.tsx | 316 +++++++- packages/vue-store/tests/test.test-d.ts | 84 +- pnpm-lock.yaml | 763 +++++++++++++++--- 270 files changed, 10469 insertions(+), 906 deletions(-) create mode 100644 docs/framework/angular/reference/functions/injectAtom.md create mode 100644 docs/framework/angular/reference/functions/injectSelector.md create mode 100644 docs/framework/angular/reference/functions/injectSetValue.md create mode 100644 docs/framework/angular/reference/functions/injectStoreActions.md create mode 100644 docs/framework/angular/reference/functions/injectValue.md create mode 100644 docs/framework/angular/reference/functions/shallow.md create mode 100644 docs/framework/angular/reference/interfaces/InjectSelectorOptions.md create mode 100644 docs/framework/preact/reference/functions/createStoreContext.md create mode 100644 docs/framework/preact/reference/functions/useAtom.md create mode 100644 docs/framework/preact/reference/functions/useCreateAtom.md create mode 100644 docs/framework/preact/reference/functions/useCreateStore.md create mode 100644 docs/framework/preact/reference/functions/useSelector.md create mode 100644 docs/framework/preact/reference/functions/useSetValue.md create mode 100644 docs/framework/preact/reference/functions/useStoreActions.md create mode 100644 docs/framework/preact/reference/functions/useValue.md create mode 100644 docs/framework/preact/reference/interfaces/UseSelectorOptions.md create mode 100644 docs/framework/solid/reference/functions/useAtom.md create mode 100644 docs/framework/solid/reference/functions/useSelector.md create mode 100644 docs/framework/solid/reference/functions/useSetValue.md create mode 100644 docs/framework/solid/reference/functions/useStoreActions.md create mode 100644 docs/framework/solid/reference/functions/useValue.md create mode 100644 docs/framework/solid/reference/interfaces/UseSelectorOptions.md create mode 100644 docs/framework/svelte/reference/functions/useAtom.md create mode 100644 docs/framework/svelte/reference/functions/useSelector.md create mode 100644 docs/framework/svelte/reference/functions/useSetValue.md create mode 100644 docs/framework/svelte/reference/functions/useStoreActions.md create mode 100644 docs/framework/svelte/reference/functions/useValue.md create mode 100644 docs/framework/svelte/reference/interfaces/UseSelectorOptions.md create mode 100644 docs/framework/vue/reference/functions/useAtom.md create mode 100644 docs/framework/vue/reference/functions/useSelector.md create mode 100644 docs/framework/vue/reference/functions/useSetValue.md create mode 100644 docs/framework/vue/reference/functions/useStoreActions.md create mode 100644 docs/framework/vue/reference/functions/useValue.md create mode 100644 docs/framework/vue/reference/interfaces/UseSelectorOptions.md create mode 100644 examples/angular/atoms/README.md create mode 100644 examples/angular/atoms/angular.json create mode 100644 examples/angular/atoms/package.json create mode 100644 examples/angular/atoms/src/app/app.component.ts create mode 100644 examples/angular/atoms/src/index.html create mode 100644 examples/angular/atoms/src/main.ts create mode 100644 examples/angular/atoms/tsconfig.app.json create mode 100644 examples/angular/atoms/tsconfig.json create mode 100644 examples/angular/atoms/tsconfig.spec.json create mode 100644 examples/angular/store-actions/README.md create mode 100644 examples/angular/store-actions/angular.json create mode 100644 examples/angular/store-actions/package.json create mode 100644 examples/angular/store-actions/src/app/app.component.ts create mode 100644 examples/angular/store-actions/src/index.html create mode 100644 examples/angular/store-actions/src/main.ts create mode 100644 examples/angular/store-actions/tsconfig.app.json create mode 100644 examples/angular/store-actions/tsconfig.json create mode 100644 examples/angular/store-actions/tsconfig.spec.json create mode 100644 examples/angular/store-context/README.md create mode 100644 examples/angular/store-context/angular.json create mode 100644 examples/angular/store-context/package.json create mode 100644 examples/angular/store-context/src/app/app.component.ts create mode 100644 examples/angular/store-context/src/index.html create mode 100644 examples/angular/store-context/src/main.ts create mode 100644 examples/angular/store-context/tsconfig.app.json create mode 100644 examples/angular/store-context/tsconfig.json create mode 100644 examples/angular/store-context/tsconfig.spec.json create mode 100644 examples/angular/stores/README.md create mode 100644 examples/angular/stores/angular.json create mode 100644 examples/angular/stores/package.json create mode 100644 examples/angular/stores/src/app/app.component.ts create mode 100644 examples/angular/stores/src/index.html create mode 100644 examples/angular/stores/src/main.ts create mode 100644 examples/angular/stores/tsconfig.app.json create mode 100644 examples/angular/stores/tsconfig.json create mode 100644 examples/angular/stores/tsconfig.spec.json create mode 100644 examples/preact/atoms/README.md create mode 100644 examples/preact/atoms/index.html create mode 100644 examples/preact/atoms/package.json create mode 100644 examples/preact/atoms/src/index.tsx create mode 100644 examples/preact/atoms/tsconfig.json create mode 100644 examples/preact/atoms/vite.config.ts create mode 100644 examples/preact/store-actions/README.md create mode 100644 examples/preact/store-actions/index.html create mode 100644 examples/preact/store-actions/package.json create mode 100644 examples/preact/store-actions/src/index.tsx create mode 100644 examples/preact/store-actions/tsconfig.json create mode 100644 examples/preact/store-actions/vite.config.ts create mode 100644 examples/preact/store-context/README.md create mode 100644 examples/preact/store-context/index.html create mode 100644 examples/preact/store-context/package.json create mode 100644 examples/preact/store-context/src/index.tsx create mode 100644 examples/preact/store-context/tsconfig.json create mode 100644 examples/preact/store-context/vite.config.ts create mode 100644 examples/preact/stores/README.md create mode 100644 examples/preact/stores/index.html create mode 100644 examples/preact/stores/package.json create mode 100644 examples/preact/stores/src/index.tsx create mode 100644 examples/preact/stores/tsconfig.json create mode 100644 examples/preact/stores/vite.config.ts create mode 100644 examples/solid/atoms/README.md create mode 100644 examples/solid/atoms/index.html create mode 100644 examples/solid/atoms/package.json create mode 100644 examples/solid/atoms/src/index.tsx create mode 100644 examples/solid/atoms/tsconfig.json create mode 100644 examples/solid/atoms/vite.config.ts create mode 100644 examples/solid/store-actions/README.md create mode 100644 examples/solid/store-actions/index.html create mode 100644 examples/solid/store-actions/package.json create mode 100644 examples/solid/store-actions/src/index.tsx create mode 100644 examples/solid/store-actions/tsconfig.json create mode 100644 examples/solid/store-actions/vite.config.ts create mode 100644 examples/solid/store-context/README.md create mode 100644 examples/solid/store-context/index.html create mode 100644 examples/solid/store-context/package.json create mode 100644 examples/solid/store-context/src/index.tsx create mode 100644 examples/solid/store-context/tsconfig.json create mode 100644 examples/solid/store-context/vite.config.ts create mode 100644 examples/solid/stores/README.md create mode 100644 examples/solid/stores/index.html create mode 100644 examples/solid/stores/package.json create mode 100644 examples/solid/stores/src/index.tsx create mode 100644 examples/solid/stores/tsconfig.json create mode 100644 examples/solid/stores/vite.config.ts create mode 100644 examples/svelte/atoms/README.md create mode 100644 examples/svelte/atoms/index.html create mode 100644 examples/svelte/atoms/package.json create mode 100644 examples/svelte/atoms/src/App.svelte create mode 100644 examples/svelte/atoms/src/main.ts create mode 100644 examples/svelte/atoms/src/vite-env.d.ts create mode 100644 examples/svelte/atoms/svelte.config.js create mode 100644 examples/svelte/atoms/tsconfig.json create mode 100644 examples/svelte/atoms/tsconfig.node.json create mode 100644 examples/svelte/atoms/vite.config.ts create mode 100644 examples/svelte/store-actions/README.md create mode 100644 examples/svelte/store-actions/index.html create mode 100644 examples/svelte/store-actions/package.json create mode 100644 examples/svelte/store-actions/src/App.svelte create mode 100644 examples/svelte/store-actions/src/main.ts create mode 100644 examples/svelte/store-actions/src/vite-env.d.ts create mode 100644 examples/svelte/store-actions/svelte.config.js create mode 100644 examples/svelte/store-actions/tsconfig.json create mode 100644 examples/svelte/store-actions/tsconfig.node.json create mode 100644 examples/svelte/store-actions/vite.config.ts create mode 100644 examples/svelte/store-context/README.md create mode 100644 examples/svelte/store-context/index.html create mode 100644 examples/svelte/store-context/package.json create mode 100644 examples/svelte/store-context/src/App.svelte create mode 100644 examples/svelte/store-context/src/AtomSection.svelte create mode 100644 examples/svelte/store-context/src/StoreSection.svelte create mode 100644 examples/svelte/store-context/src/context.ts create mode 100644 examples/svelte/store-context/src/main.ts create mode 100644 examples/svelte/store-context/src/vite-env.d.ts create mode 100644 examples/svelte/store-context/svelte.config.js create mode 100644 examples/svelte/store-context/tsconfig.json create mode 100644 examples/svelte/store-context/tsconfig.node.json create mode 100644 examples/svelte/store-context/vite.config.ts create mode 100644 examples/svelte/stores/README.md create mode 100644 examples/svelte/stores/index.html create mode 100644 examples/svelte/stores/package.json create mode 100644 examples/svelte/stores/src/App.svelte create mode 100644 examples/svelte/stores/src/main.ts create mode 100644 examples/svelte/stores/src/vite-env.d.ts create mode 100644 examples/svelte/stores/svelte.config.js create mode 100644 examples/svelte/stores/tsconfig.json create mode 100644 examples/svelte/stores/tsconfig.node.json create mode 100644 examples/svelte/stores/vite.config.ts create mode 100644 examples/vue/atoms/README.md create mode 100644 examples/vue/atoms/index.html create mode 100644 examples/vue/atoms/package.json create mode 100644 examples/vue/atoms/src/App.vue create mode 100644 examples/vue/atoms/src/main.ts create mode 100644 examples/vue/atoms/src/shims-vue.d.ts create mode 100644 examples/vue/atoms/tsconfig.json create mode 100644 examples/vue/atoms/vite.config.ts create mode 100644 examples/vue/store-actions/README.md create mode 100644 examples/vue/store-actions/index.html create mode 100644 examples/vue/store-actions/package.json create mode 100644 examples/vue/store-actions/src/App.vue create mode 100644 examples/vue/store-actions/src/main.ts create mode 100644 examples/vue/store-actions/src/shims-vue.d.ts create mode 100644 examples/vue/store-actions/tsconfig.json create mode 100644 examples/vue/store-actions/vite.config.ts create mode 100644 examples/vue/store-context/README.md create mode 100644 examples/vue/store-context/index.html create mode 100644 examples/vue/store-context/package.json create mode 100644 examples/vue/store-context/src/App.vue create mode 100644 examples/vue/store-context/src/main.ts create mode 100644 examples/vue/store-context/src/shims-vue.d.ts create mode 100644 examples/vue/store-context/tsconfig.json create mode 100644 examples/vue/store-context/vite.config.ts create mode 100644 examples/vue/stores/README.md create mode 100644 examples/vue/stores/index.html create mode 100644 examples/vue/stores/package.json create mode 100644 examples/vue/stores/src/App.vue create mode 100644 examples/vue/stores/src/main.ts create mode 100644 examples/vue/stores/src/shims-vue.d.ts create mode 100644 examples/vue/stores/tsconfig.json create mode 100644 examples/vue/stores/vite.config.ts create mode 100644 packages/preact-store/src/createStoreContext.tsx create mode 100644 packages/preact-store/src/shallow.ts create mode 100644 packages/preact-store/src/useAtom.ts create mode 100644 packages/preact-store/src/useCreateAtom.ts create mode 100644 packages/preact-store/src/useCreateStore.ts create mode 100644 packages/preact-store/src/useSelector.ts create mode 100644 packages/preact-store/src/useSetValue.ts create mode 100644 packages/preact-store/src/useStore.ts create mode 100644 packages/preact-store/src/useStoreActions.ts create mode 100644 packages/preact-store/src/useValue.ts create mode 100644 packages/solid-store/src/shallow.ts create mode 100644 packages/solid-store/src/useAtom.ts create mode 100644 packages/solid-store/src/useSelector.ts create mode 100644 packages/solid-store/src/useSetValue.ts create mode 100644 packages/solid-store/src/useStore.ts create mode 100644 packages/solid-store/src/useStoreActions.ts create mode 100644 packages/solid-store/src/useValue.ts create mode 100644 packages/svelte-store/tests/Value.test.svelte create mode 100644 packages/svelte-store/tests/test.test-d.ts create mode 100644 packages/vue-store/src/shallow.ts create mode 100644 packages/vue-store/src/useAtom.ts create mode 100644 packages/vue-store/src/useSelector.ts create mode 100644 packages/vue-store/src/useSetValue.ts create mode 100644 packages/vue-store/src/useStore.ts create mode 100644 packages/vue-store/src/useStoreActions.ts create mode 100644 packages/vue-store/src/useValue.ts diff --git a/.gitignore b/.gitignore index cc376bd6..182bead0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ dist .env.test.local .env.production.local .next +.angular/* +.cache/* npm-debug.log* yarn-debug.log* @@ -41,3 +43,16 @@ stats.html vite.config.js.timestamp-* vite.config.ts.timestamp-* + +examples/angular/atoms/.angular/cache/* +examples/angular/atoms/.angular/cache/21.2.7/atoms/angular-compiler.db +examples/angular/atoms/.angular/cache/21.2.7/atoms/angular-compiler.db-lock +examples/angular/store-actions/.angular/cache/21.2.7/store-actions/.tsbuildinfo +examples/angular/store-actions/.angular/cache/21.2.7/store-actions/angular-compiler.db +examples/angular/store-actions/.angular/cache/21.2.7/store-actions/angular-compiler.db-lock +examples/angular/store-context/.angular/cache/21.2.7/store-context/.tsbuildinfo +examples/angular/store-context/.angular/cache/21.2.7/store-context/angular-compiler.db +examples/angular/store-context/.angular/cache/21.2.7/store-context/angular-compiler.db-lock +examples/angular/stores/.angular/cache/21.2.7/stores/.tsbuildinfo +examples/angular/stores/.angular/cache/21.2.7/stores/angular-compiler.db +examples/angular/stores/.angular/cache/21.2.7/stores/angular-compiler.db-lock diff --git a/docs/config.json b/docs/config.json index 23c3c93a..e84654ae 100644 --- a/docs/config.json +++ b/docs/config.json @@ -83,88 +83,171 @@ "label": "API Reference", "children": [ { - "label": "JavaScript Reference", + "label": "Core API Reference", "to": "reference/index" + } + ], + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "React Hooks", + "to": "framework/react/reference/index" + } + ] + }, + { + "label": "vue", + "children": [ + { + "label": "Vue Hooks", + "to": "framework/vue/reference/index" + } + ] + }, + { + "label": "solid", + "children": [ + { + "label": "Solid Hooks", + "to": "framework/solid/reference/index" + } + ] }, { - "label": "Classes / Store", + "label": "angular", + "children": [ + { + "label": "Angular Inject API", + "to": "framework/angular/reference/index" + } + ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Svelte Hooks", + "to": "framework/svelte/reference/index" + } + ] + }, + { + "label": "preact", + "children": [ + { + "label": "Preact Hooks", + "to": "framework/preact/reference/index" + } + ] + } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Store API Reference", + "children": [ + { + "label": "Store", "to": "reference/classes/Store" }, { - "label": "Interfaces / Atom", - "to": "reference/interfaces/atom" + "label": "ReadonlyStore", + "to": "reference/classes/ReadonlyStore" + }, + { + "label": "Atom", + "to": "reference/interfaces/Atom" + }, + { + "label": "AtomOptions", + "to": "reference/interfaces/AtomOptions" + }, + { + "label": "BaseAtom", + "to": "reference/interfaces/BaseAtom" + }, + { + "label": "InternalBaseAtom", + "to": "reference/interfaces/InternalBaseAtom" + }, + { + "label": "InternalReadonlyAtom", + "to": "reference/interfaces/InternalReadonlyAtom" }, { - "label": "Interfaces / AtomOptions", - "to": "reference/interfaces/atomoptions" + "label": "InteropSubscribable", + "to": "reference/interfaces/InteropSubscribable" }, { - "label": "Interfaces / BaseAtom", - "to": "reference/interfaces/baseatom" + "label": "Readable", + "to": "reference/interfaces/Readable" }, { - "label": "Interfaces / InternalBaseAtom", - "to": "reference/interfaces/internalbaseatom" + "label": "ReadonlyAtom", + "to": "reference/interfaces/ReadonlyAtom" }, { - "label": "Interfaces / InternalReadonlyAtom", - "to": "reference/interfaces/internalreadonlyatom" + "label": "StoreActionsApi", + "to": "reference/interfaces/StoreActionsApi" }, { - "label": "Interfaces / InteropSubscribable", - "to": "reference/interfaces/interopsubscribable" + "label": "Subscribable", + "to": "reference/interfaces/Subscribable" }, { - "label": "Interfaces / Readable", - "to": "reference/interfaces/readable" + "label": "Subscription", + "to": "reference/interfaces/Subscription" }, { - "label": "Interfaces / ReadonlyAtom", - "to": "reference/interfaces/readonlyatom" + "label": "AnyAtom", + "to": "reference/type-aliases/AnyAtom" }, { - "label": "Interfaces / Subscribable", - "to": "reference/interfaces/subscribable" + "label": "Observer", + "to": "reference/type-aliases/Observer" }, { - "label": "Interfaces / Subscription", - "to": "reference/interfaces/subscription" + "label": "Selection", + "to": "reference/type-aliases/Selection" }, { - "label": "Type Aliases / AnyAtom", - "to": "reference/type-aliases/anyatom" + "label": "StoreAction", + "to": "reference/type-aliases/StoreAction" }, { - "label": "Type Aliases / Observer", - "to": "reference/type-aliases/observer" + "label": "StoreActionMap", + "to": "reference/type-aliases/StoreActionMap" }, { - "label": "Type Aliases / Selection", - "to": "reference/type-aliases/selection" + "label": "StoreActionsFactory", + "to": "reference/type-aliases/StoreActionsFactory" }, { - "label": "Functions / batch", + "label": "batch", "to": "reference/functions/batch" }, { - "label": "Functions / createAsyncAtom", - "to": "reference/functions/createasyncatom" + "label": "createAsyncAtom", + "to": "reference/functions/createAsyncAtom" }, { - "label": "Functions / createAtom", - "to": "reference/functions/createatom" + "label": "createAtom", + "to": "reference/functions/createAtom" }, { - "label": "Functions / createStore", - "to": "reference/functions/createstore" + "label": "createStore", + "to": "reference/functions/createStore" }, { - "label": "Functions / flush", + "label": "flush", "to": "reference/functions/flush" }, { - "label": "Functions / toObserver", - "to": "reference/functions/toobserver" + "label": "toObserver", + "to": "reference/functions/toObserver" } ], "frameworks": [ @@ -172,44 +255,48 @@ "label": "react", "children": [ { - "label": "React Reference", - "to": "framework/react/reference/index" - }, - { - "label": "Functions / createStoreContext", + "label": "createStoreContext", "to": "framework/react/reference/functions/createStoreContext" }, { - "label": "Functions / useCreateAtom", + "label": "useCreateAtom", "to": "framework/react/reference/functions/useCreateAtom" }, { - "label": "Functions / useValue", + "label": "useCreateStore", + "to": "framework/react/reference/functions/useCreateStore" + }, + { + "label": "useSelector", + "to": "framework/react/reference/functions/useSelector" + }, + { + "label": "useValue", "to": "framework/react/reference/functions/useValue" }, { - "label": "Functions / useSetValue", + "label": "useSetValue", "to": "framework/react/reference/functions/useSetValue" }, { - "label": "Functions / useAtom", + "label": "useAtom", "to": "framework/react/reference/functions/useAtom" }, { - "label": "Functions / useCreateStore", - "to": "framework/react/reference/functions/useCreateStore" + "label": "useStoreActions", + "to": "framework/react/reference/functions/useStoreActions" }, { - "label": "Functions / useSelector", - "to": "framework/react/reference/functions/useSelector" + "label": "useStore (deprecated)", + "to": "framework/react/reference/functions/useStore" }, { - "label": "Variables / useStore (deprecated)", - "to": "framework/react/reference/variables/useStore" + "label": "shallow", + "to": "framework/react/reference/functions/shallow" }, { - "label": "Functions / shallow", - "to": "framework/react/reference/functions/shallow" + "label": "UseSelectorOptions", + "to": "framework/react/reference/interfaces/UseSelectorOptions" } ] }, @@ -217,16 +304,36 @@ "label": "vue", "children": [ { - "label": "Vue Reference", - "to": "framework/vue/reference/index" + "label": "useSelector", + "to": "framework/vue/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/vue/reference/functions/useValue" }, { - "label": "Functions / useStore", + "label": "useSetValue", + "to": "framework/vue/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/vue/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/vue/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/vue/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/vue/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/vue/reference/interfaces/UseSelectorOptions" } ] }, @@ -234,12 +341,36 @@ "label": "solid", "children": [ { - "label": "Solid Reference", - "to": "framework/solid/reference/index" + "label": "useSelector", + "to": "framework/solid/reference/functions/useSelector" }, { - "label": "Functions / useStore", + "label": "useValue", + "to": "framework/solid/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/solid/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/solid/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/solid/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/solid/reference/functions/useStore" + }, + { + "label": "shallow", + "to": "framework/solid/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/solid/reference/interfaces/UseSelectorOptions" } ] }, @@ -247,12 +378,36 @@ "label": "angular", "children": [ { - "label": "Angular Reference", - "to": "framework/angular/reference/index" + "label": "injectSelector", + "to": "framework/angular/reference/functions/injectSelector" + }, + { + "label": "injectValue", + "to": "framework/angular/reference/functions/injectValue" + }, + { + "label": "injectSetValue", + "to": "framework/angular/reference/functions/injectSetValue" + }, + { + "label": "injectAtom", + "to": "framework/angular/reference/functions/injectAtom" }, { - "label": "Functions / injectStore", + "label": "injectStoreActions", + "to": "framework/angular/reference/functions/injectStoreActions" + }, + { + "label": "injectStore (deprecated)", "to": "framework/angular/reference/functions/injectStore" + }, + { + "label": "shallow", + "to": "framework/angular/reference/functions/shallow" + }, + { + "label": "InjectSelectorOptions", + "to": "framework/angular/reference/interfaces/InjectSelectorOptions" } ] }, @@ -260,16 +415,36 @@ "label": "svelte", "children": [ { - "label": "Svelte Reference", - "to": "framework/svelte/reference/index" + "label": "useSelector", + "to": "framework/svelte/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/svelte/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/svelte/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/svelte/reference/functions/useAtom" }, { - "label": "Functions / useStore", + "label": "useStoreActions", + "to": "framework/svelte/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/svelte/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/svelte/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/svelte/reference/interfaces/UseSelectorOptions" } ] }, @@ -277,16 +452,48 @@ "label": "preact", "children": [ { - "label": "Preact Reference", - "to": "framework/preact/reference/index" + "label": "createStoreContext", + "to": "framework/preact/reference/functions/createStoreContext" }, { - "label": "Functions / useStore", + "label": "useCreateAtom", + "to": "framework/preact/reference/functions/useCreateAtom" + }, + { + "label": "useCreateStore", + "to": "framework/preact/reference/functions/useCreateStore" + }, + { + "label": "useSelector", + "to": "framework/preact/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/preact/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/preact/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/preact/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/preact/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/preact/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/preact/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/preact/reference/interfaces/UseSelectorOptions" } ] } @@ -327,6 +534,22 @@ { "label": "Simple", "to": "framework/vue/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/vue/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/vue/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/vue/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/vue/examples/store-context" } ] }, @@ -336,6 +559,22 @@ { "label": "Simple", "to": "framework/angular/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/angular/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/angular/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/angular/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/angular/examples/store-context" } ] }, @@ -345,6 +584,22 @@ { "label": "Simple", "to": "framework/svelte/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/svelte/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/svelte/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/svelte/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/svelte/examples/store-context" } ] }, @@ -354,6 +609,22 @@ { "label": "Simple", "to": "framework/solid/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/solid/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/solid/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/solid/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/solid/examples/store-context" } ] }, @@ -363,6 +634,22 @@ { "label": "Simple", "to": "framework/preact/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/preact/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/preact/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/preact/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/preact/examples/store-context" } ] } diff --git a/docs/framework/angular/quick-start.md b/docs/framework/angular/quick-start.md index b4b48695..89e4847c 100644 --- a/docs/framework/angular/quick-start.md +++ b/docs/framework/angular/quick-start.md @@ -52,7 +52,7 @@ export function updateState(animal: 'dogs' | 'cats') { **display.component.ts** ```angular-ts -import { injectStore } from '@tanstack/angular-store'; +import { injectSelector } from '@tanstack/angular-store'; import { store } from './store'; @Component({ @@ -65,7 +65,7 @@ import { store } from './store'; }) export class Display { animal = input.required(); - count = injectStore(store, (state) => state[this.animal()]); + count = injectSelector(store, (state) => state[this.animal()]); } ``` @@ -86,3 +86,5 @@ export class Increment { updateState = updateState; } ``` + +`injectStore` remains available as a deprecated alias to `injectSelector`. diff --git a/docs/framework/angular/reference/functions/injectAtom.md b/docs/framework/angular/reference/functions/injectAtom.md new file mode 100644 index 00000000..f0346fe4 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectAtom.md @@ -0,0 +1,45 @@ +--- +id: injectAtom +title: injectAtom +--- + +# Function: injectAtom() + +```ts +function injectAtom(atom, options?): [Signal, (fn) => void & (value) => void]; +``` + +Defined in: [packages/angular-store/src/index.ts:197](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L197) + +Returns the current atom signal together with a setter. + +Use this when a component needs to both read and update the same writable +atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TValue`\> + +## Returns + +\[`Signal`\<`TValue`\>, (`fn`) => `void` & (`value`) => `void`\] + +## Example + +```ts +readonly atomTuple = injectAtom(countAtom) +readonly count = this.atomTuple[0] +readonly setCount = this.atomTuple[1] +``` diff --git a/docs/framework/angular/reference/functions/injectSelector.md b/docs/framework/angular/reference/functions/injectSelector.md new file mode 100644 index 00000000..f3c708f3 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectSelector.md @@ -0,0 +1,58 @@ +--- +id: injectSelector +title: injectSelector +--- + +# Function: injectSelector() + +```ts +function injectSelector( + source, + selector, +options?): Signal; +``` + +Defined in: [packages/angular-store/src/index.ts:107](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L107) + +Selects a slice of state from an atom or store and returns it as an Angular +signal. + +This is the primary Angular read hook for TanStack Store. + +## Type Parameters + +### TState + +`TState` + +### TSelected + +`TSelected` = `NoInfer`\<`TState`\> + +## Parameters + +### source + +`SelectionSource`\<`TState`\> + +### selector + +(`state`) => `TSelected` + +### options? + +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TSelected`\> + +## Returns + +`Signal`\<`TSelected`\> + +## Examples + +```ts +readonly count = injectSelector(counterStore, (state) => state.count) +``` + +```ts +readonly doubled = injectSelector(countAtom, (value) => value * 2) +``` diff --git a/docs/framework/angular/reference/functions/injectSetValue.md b/docs/framework/angular/reference/functions/injectSetValue.md new file mode 100644 index 00000000..4667fdd8 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectSetValue.md @@ -0,0 +1,108 @@ +--- +id: injectSetValue +title: injectSetValue +--- + +# Function: injectSetValue() + +## Call Signature + +```ts +function injectSetValue(source): (fn) => void & (value) => void; +``` + +Defined in: [packages/angular-store/src/index.ts:162](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L162) + +Returns a stable setter for a writable atom or store. + +Writable atoms preserve their native `set` contract. Writable stores +preserve their native `setState` contract. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### source + +`Atom`\<`TValue`\> + +### Returns + +(`fn`) => `void` & (`value`) => `void` + +### Examples + +```ts +readonly setCount = injectSetValue(countAtom) + +increment() { + this.setCount((prev) => prev + 1) +} +``` + +```ts +readonly setState = injectSetValue(counterStore) +``` + +## Call Signature + +```ts +function injectSetValue(source): (updater) => void; +``` + +Defined in: [packages/angular-store/src/index.ts:165](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L165) + +Returns a stable setter for a writable atom or store. + +Writable atoms preserve their native `set` contract. Writable stores +preserve their native `setState` contract. + +### Type Parameters + +#### TValue + +`TValue` + +#### TActions + +`TActions` *extends* `StoreActionMap` + +### Parameters + +#### source + +`Store`\<`TValue`, `TActions`\> + +### Returns + +```ts +(updater): void; +``` + +#### Parameters + +##### updater + +(`prev`) => `TValue` + +#### Returns + +`void` + +### Examples + +```ts +readonly setCount = injectSetValue(countAtom) + +increment() { + this.setCount((prev) => prev + 1) +} +``` + +```ts +readonly setState = injectSetValue(counterStore) +``` diff --git a/docs/framework/angular/reference/functions/injectStore.md b/docs/framework/angular/reference/functions/injectStore.md index 0219a908..78fbb452 100644 --- a/docs/framework/angular/reference/functions/injectStore.md +++ b/docs/framework/angular/reference/functions/injectStore.md @@ -3,9 +3,7 @@ id: injectStore title: injectStore --- -# Function: injectStore() - -## Call Signature +# ~~Function: injectStore()~~ ```ts function injectStore( @@ -14,71 +12,44 @@ function injectStore( options?): Signal; ``` -Defined in: [index.ts:16](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L16) +Defined in: [packages/angular-store/src/index.ts:238](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L238) + +Deprecated alias for [injectSelector](injectSelector.md). -### Type Parameters +## Type Parameters -#### TState +### TState `TState` -#### TSelected +### TSelected `TSelected` = `NoInfer`\<`TState`\> -### Parameters +## Parameters -#### store +### store -`Atom`\<`TState`\> +`SelectionSource`\<`TState`\> -#### selector? +### selector? (`state`) => `TSelected` -#### options? +### options? -`CreateSignalOptions`\<`TSelected`\> & `object` +`CompatibilityInjectStoreOptions`\<`TSelected`\> -### Returns +## Returns `Signal`\<`TSelected`\> -## Call Signature +## Example ```ts -function injectStore( - store, - selector?, -options?): Signal; +readonly count = injectStore(counterStore, (state) => state.count) ``` -Defined in: [index.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L21) +## Deprecated -### Type Parameters - -#### TState - -`TState` - -#### TSelected - -`TSelected` = `NoInfer`\<`TState`\> - -### Parameters - -#### store - -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> - -#### selector? - -(`state`) => `TSelected` - -#### options? - -`CreateSignalOptions`\<`TSelected`\> & `object` - -### Returns - -`Signal`\<`TSelected`\> +Use `injectSelector` instead. diff --git a/docs/framework/angular/reference/functions/injectStoreActions.md b/docs/framework/angular/reference/functions/injectStoreActions.md new file mode 100644 index 00000000..4674e385 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectStoreActions.md @@ -0,0 +1,47 @@ +--- +id: injectStoreActions +title: injectStoreActions +--- + +# Function: injectStoreActions() + +```ts +function injectStoreActions(store): TActions; +``` + +Defined in: [packages/angular-store/src/index.ts:222](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L222) + +Returns the stable actions bag from a writable store created with actions. + +Use this when a component only needs to call store actions and should not +subscribe to store state. + +## Type Parameters + +### TValue + +`TValue` + +### TActions + +`TActions` *extends* `StoreActionMap` + +## Parameters + +### store + +`Store`\<`TValue`, `TActions`\> + +## Returns + +`TActions` + +## Example + +```ts +readonly actions = injectStoreActions(counterStore) + +increment() { + this.actions.increment() +} +``` diff --git a/docs/framework/angular/reference/functions/injectValue.md b/docs/framework/angular/reference/functions/injectValue.md new file mode 100644 index 00000000..d6e26c26 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectValue.md @@ -0,0 +1,46 @@ +--- +id: injectValue +title: injectValue +--- + +# Function: injectValue() + +```ts +function injectValue(source, options?): Signal; +``` + +Defined in: [packages/angular-store/src/index.ts:131](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L131) + +Returns the current value signal for an atom or store. + +This is the whole-value counterpart to [injectSelector](injectSelector.md). + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TValue`\> + +## Returns + +`Signal`\<`TValue`\> + +## Examples + +```ts +readonly count = injectValue(countAtom) +``` + +```ts +readonly state = injectValue(counterStore) +``` diff --git a/docs/framework/angular/reference/functions/shallow.md b/docs/framework/angular/reference/functions/shallow.md new file mode 100644 index 00000000..de4d5f96 --- /dev/null +++ b/docs/framework/angular/reference/functions/shallow.md @@ -0,0 +1,32 @@ +--- +id: shallow +title: shallow +--- + +# Function: shallow() + +```ts +function shallow(objA, objB): boolean; +``` + +Defined in: [packages/angular-store/src/index.ts:258](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L258) + +## Type Parameters + +### T + +`T` + +## Parameters + +### objA + +`T` + +### objB + +`T` + +## Returns + +`boolean` diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md index b77cf408..e162c8cd 100644 --- a/docs/framework/angular/reference/index.md +++ b/docs/framework/angular/reference/index.md @@ -5,6 +5,16 @@ title: "@tanstack/angular-store" # @tanstack/angular-store +## Interfaces + +- [InjectSelectorOptions](interfaces/InjectSelectorOptions.md) + ## Functions -- [injectStore](functions/injectStore.md) +- [injectAtom](functions/injectAtom.md) +- [injectSelector](functions/injectSelector.md) +- [injectSetValue](functions/injectSetValue.md) +- [~~injectStore~~](functions/injectStore.md) +- [injectStoreActions](functions/injectStoreActions.md) +- [injectValue](functions/injectValue.md) +- [shallow](functions/shallow.md) diff --git a/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md b/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md new file mode 100644 index 00000000..e2e5ee5e --- /dev/null +++ b/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md @@ -0,0 +1,70 @@ +--- +id: InjectSelectorOptions +title: InjectSelectorOptions +--- + +# Interface: InjectSelectorOptions\ + +Defined in: [packages/angular-store/src/index.ts:20](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L20) + +## Extends + +- `Omit`\<`CreateSignalOptions`\<`TSelected`\>, `"equal"`\> + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: [packages/angular-store/src/index.ts:24](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L24) + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` + +*** + +### debugName? + +```ts +optional debugName: string; +``` + +Defined in: node\_modules/.pnpm/@angular+core@21.2.8\_@angular+compiler@21.2.8\_rxjs@7.8.2\_zone.js@0.16.1/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:54 + +A debug name for the signal. Used in Angular DevTools to identify the signal. + +#### Inherited from + +```ts +Omit.debugName +``` + +*** + +### injector? + +```ts +optional injector: Injector; +``` + +Defined in: [packages/angular-store/src/index.ts:25](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L25) diff --git a/docs/framework/preact/quick-start.md b/docs/framework/preact/quick-start.md index 841fcab7..a09c6ea0 100644 --- a/docs/framework/preact/quick-start.md +++ b/docs/framework/preact/quick-start.md @@ -7,7 +7,7 @@ The basic preact app example to get started with the TanStack preact-store. ```tsx import { render } from "preact"; -import { createStore, useStore } from "@tanstack/preact-store"; +import { createStore, useSelector } from "@tanstack/preact-store"; // You can instantiate the store outside of Preact components too! export const store = createStore({ @@ -18,7 +18,7 @@ export const store = createStore({ // This will only re-render when `state[animal]` changes. If an unrelated store property changes, it won't re-render const Display = ({ animal }) => { - const count = useStore(store, (state) => state[animal]); + const count = useSelector(store, (state) => state[animal]); return
{`${animal}: ${count}`}
; }; @@ -53,3 +53,4 @@ function App() { render(, document.getElementById("root")); ``` +`useStore` remains available as a deprecated alias to `useSelector`. diff --git a/docs/framework/preact/reference/functions/createStoreContext.md b/docs/framework/preact/reference/functions/createStoreContext.md new file mode 100644 index 00000000..e6e368f9 --- /dev/null +++ b/docs/framework/preact/reference/functions/createStoreContext.md @@ -0,0 +1,95 @@ +--- +id: createStoreContext +title: createStoreContext +--- + +# Function: createStoreContext() + +```ts +function createStoreContext(): object; +``` + +Defined in: preact-store/src/createStoreContext.tsx:44 + +Creates a typed Preact context for sharing a bundle of atoms and stores with +a subtree. + +The returned `StoreProvider` only transports the provided object through +Preact context. Consumers destructure the contextual atoms and stores, then +compose them with the existing hooks like [useSelector](useSelector.md), +[useValue](useValue.md), [useSetValue](useSetValue.md), and [useAtom](useAtom.md). + +The object shape is preserved exactly, so keyed atoms and stores remain fully +typed when read back with `useStoreContext()`. + +## Type Parameters + +### TValue + +`TValue` *extends* `object` + +## Returns + +`object` + +### StoreProvider() + +```ts +StoreProvider: (__namedParameters) => Element; +``` + +#### Parameters + +##### \_\_namedParameters + +###### children? + +`ComponentChildren` + +###### value + +`TValue` + +#### Returns + +`Element` + +### useStoreContext() + +```ts +useStoreContext: () => TValue; +``` + +#### Returns + +`TValue` + +## Example + +```tsx +const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: Atom + totalsStore: Store<{ count: number }> +}>() + +function CountButton() { + const { countAtom, totalsStore } = useStoreContext() + const count = useValue(countAtom) + const total = useSelector(totalsStore, (state) => state.count) + + return ( + + ) +} +``` + +## Throws + +When `useStoreContext()` is called outside the matching `StoreProvider`. diff --git a/docs/framework/preact/reference/functions/shallow.md b/docs/framework/preact/reference/functions/shallow.md index eb6e75c9..007453d0 100644 --- a/docs/framework/preact/reference/functions/shallow.md +++ b/docs/framework/preact/reference/functions/shallow.md @@ -9,7 +9,7 @@ title: shallow function shallow(objA, objB): boolean; ``` -Defined in: [index.ts:116](https://github.com/TanStack/store/blob/main/packages/preact-store/src/index.ts#L116) +Defined in: preact-store/src/shallow.ts:1 ## Type Parameters diff --git a/docs/framework/preact/reference/functions/useAtom.md b/docs/framework/preact/reference/functions/useAtom.md new file mode 100644 index 00000000..824f1323 --- /dev/null +++ b/docs/framework/preact/reference/functions/useAtom.md @@ -0,0 +1,49 @@ +--- +id: useAtom +title: useAtom +--- + +# Function: useAtom() + +```ts +function useAtom(atom, options?): [TValue, (fn) => void & (value) => void]; +``` + +Defined in: preact-store/src/useAtom.ts:23 + +Returns the current atom value together with a stable setter. + +Use this when a component needs to both read and update the same writable +atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +\[`TValue`, (`fn`) => `void` & (`value`) => `void`\] + +## Example + +```tsx +const [count, setCount] = useAtom(countAtom) + +return ( + +) +``` diff --git a/docs/framework/preact/reference/functions/useCreateAtom.md b/docs/framework/preact/reference/functions/useCreateAtom.md new file mode 100644 index 00000000..527b6e2c --- /dev/null +++ b/docs/framework/preact/reference/functions/useCreateAtom.md @@ -0,0 +1,104 @@ +--- +id: useCreateAtom +title: useCreateAtom +--- + +# Function: useCreateAtom() + +## Call Signature + +```ts +function useCreateAtom(getValue, options?): ReadonlyAtom; +``` + +Defined in: preact-store/src/useCreateAtom.ts:27 + +Creates a stable atom instance for the lifetime of the component. + +Pass an initial value to create a writable atom, or a getter function to +create a readonly derived atom. This mirrors createAtom, but only +creates the atom once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### getValue + +(`prev?`) => `T` + +#### options? + +`AtomOptions`\<`T`\> + +### Returns + +`ReadonlyAtom`\<`T`\> + +### Example + +```tsx +function Counter() { + const countAtom = useCreateAtom(0) + const [count, setCount] = useAtom(countAtom) + + return ( + + ) +} +``` + +## Call Signature + +```ts +function useCreateAtom(initialValue, options?): Atom; +``` + +Defined in: preact-store/src/useCreateAtom.ts:31 + +Creates a stable atom instance for the lifetime of the component. + +Pass an initial value to create a writable atom, or a getter function to +create a readonly derived atom. This mirrors createAtom, but only +creates the atom once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### initialValue + +`T` + +#### options? + +`AtomOptions`\<`T`\> + +### Returns + +`Atom`\<`T`\> + +### Example + +```tsx +function Counter() { + const countAtom = useCreateAtom(0) + const [count, setCount] = useAtom(countAtom) + + return ( + + ) +} +``` diff --git a/docs/framework/preact/reference/functions/useCreateStore.md b/docs/framework/preact/reference/functions/useCreateStore.md new file mode 100644 index 00000000..79176987 --- /dev/null +++ b/docs/framework/preact/reference/functions/useCreateStore.md @@ -0,0 +1,161 @@ +--- +id: useCreateStore +title: useCreateStore +--- + +# Function: useCreateStore() + +## Call Signature + +```ts +function useCreateStore(getValue): ReadonlyStore; +``` + +Defined in: preact-store/src/useCreateStore.ts:38 + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This mirrors createStore, but only +creates the store once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### getValue + +(`prev?`) => `T` + +### Returns + +`ReadonlyStore`\<`T`\> + +### Example + +```tsx +function Counter() { + const counterStore = useCreateStore({ count: 0 }) + const count = useSelector(counterStore, (state) => state.count) + const setState = useSetValue(counterStore) + + return ( + + ) +} +``` + +## Call Signature + +```ts +function useCreateStore(initialValue): Store; +``` + +Defined in: preact-store/src/useCreateStore.ts:41 + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This mirrors createStore, but only +creates the store once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### initialValue + +`T` + +### Returns + +`Store`\<`T`\> + +### Example + +```tsx +function Counter() { + const counterStore = useCreateStore({ count: 0 }) + const count = useSelector(counterStore, (state) => state.count) + const setState = useSetValue(counterStore) + + return ( + + ) +} +``` + +## Call Signature + +```ts +function useCreateStore(initialValue, actions): Store; +``` + +Defined in: preact-store/src/useCreateStore.ts:42 + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This mirrors createStore, but only +creates the store once per component mount. + +### Type Parameters + +#### T + +`T` + +#### TActions + +`TActions` *extends* `StoreActionMap` + +### Parameters + +#### initialValue + +`NonFunction`\<`T`\> + +#### actions + +`StoreActionsFactory`\<`T`, `TActions`\> + +### Returns + +`Store`\<`T`, `TActions`\> + +### Example + +```tsx +function Counter() { + const counterStore = useCreateStore({ count: 0 }) + const count = useSelector(counterStore, (state) => state.count) + const setState = useSetValue(counterStore) + + return ( + + ) +} +``` diff --git a/docs/framework/preact/reference/functions/useSelector.md b/docs/framework/preact/reference/functions/useSelector.md new file mode 100644 index 00000000..8a9c4343 --- /dev/null +++ b/docs/framework/preact/reference/functions/useSelector.md @@ -0,0 +1,59 @@ +--- +id: useSelector +title: useSelector +--- + +# Function: useSelector() + +```ts +function useSelector( + source, + selector, + options?): TSelected; +``` + +Defined in: preact-store/src/useSelector.ts:127 + +Selects a slice of state from an atom or store and subscribes the component +to that selection. + +This is the primary Preact read hook for TanStack Store. Use it when a +component only needs part of a source value. + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +`SelectionSource`\<`TSource`\> + +### selector + +(`snapshot`) => `TSelected` + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> + +## Returns + +`TSelected` + +## Examples + +```tsx +const count = useSelector(counterStore, (state) => state.count) +``` + +```tsx +const doubled = useSelector(countAtom, (value) => value * 2) +``` diff --git a/docs/framework/preact/reference/functions/useSetValue.md b/docs/framework/preact/reference/functions/useSetValue.md new file mode 100644 index 00000000..f66e138c --- /dev/null +++ b/docs/framework/preact/reference/functions/useSetValue.md @@ -0,0 +1,104 @@ +--- +id: useSetValue +title: useSetValue +--- + +# Function: useSetValue() + +## Call Signature + +```ts +function useSetValue(source): (fn) => void & (value) => void; +``` + +Defined in: preact-store/src/useSetValue.ts:22 + +Returns a stable setter for a writable atom or store. + +Writable atoms preserve their native `set` contract. Writable stores +preserve their native `setState` contract. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### source + +`Atom`\<`TValue`\> + +### Returns + +(`fn`) => `void` & (`value`) => `void` + +### Examples + +```tsx +const setCount = useSetValue(countAtom) +setCount((prev) => prev + 1) +``` + +```tsx +const setState = useSetValue(counterStore) +setState((state) => ({ ...state, count: state.count + 1 })) +``` + +## Call Signature + +```ts +function useSetValue(source): (updater) => void; +``` + +Defined in: preact-store/src/useSetValue.ts:23 + +Returns a stable setter for a writable atom or store. + +Writable atoms preserve their native `set` contract. Writable stores +preserve their native `setState` contract. + +### Type Parameters + +#### TValue + +`TValue` + +#### TActions + +`TActions` *extends* `StoreActionMap` + +### Parameters + +#### source + +`Store`\<`TValue`, `TActions`\> + +### Returns + +```ts +(updater): void; +``` + +#### Parameters + +##### updater + +(`prev`) => `TValue` + +#### Returns + +`void` + +### Examples + +```tsx +const setCount = useSetValue(countAtom) +setCount((prev) => prev + 1) +``` + +```tsx +const setState = useSetValue(counterStore) +setState((state) => ({ ...state, count: state.count + 1 })) +``` diff --git a/docs/framework/preact/reference/functions/useStore.md b/docs/framework/preact/reference/functions/useStore.md index b9a67a5d..a8bd22a4 100644 --- a/docs/framework/preact/reference/functions/useStore.md +++ b/docs/framework/preact/reference/functions/useStore.md @@ -3,41 +3,59 @@ id: useStore title: useStore --- -# Function: useStore() +# ~~Function: useStore()~~ ```ts -function useStore( - store, +function useStore( + source, selector, - options): TSelected; + compare?): TSelected; ``` -Defined in: [index.ts:100](https://github.com/TanStack/store/blob/main/packages/preact-store/src/index.ts#L100) +Defined in: preact-store/src/useStore.ts:13 + +Deprecated alias for [useSelector](useSelector.md). ## Type Parameters -### TState +### TSource -`TState` +`TSource` ### TSelected -`TSelected` = `NoInfer`\<`TState`\> +`TSelected` ## Parameters -### store +### source + +#### get + +() => `TSource` + +#### subscribe -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> +(`listener`) => `object` ### selector -(`state`) => `TSelected` +(`snapshot`) => `TSelected` -### options +### compare? -`UseStoreOptions`\<`TSelected`\> = `{}` +(`a`, `b`) => `boolean` ## Returns `TSelected` + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/preact/reference/functions/useStoreActions.md b/docs/framework/preact/reference/functions/useStoreActions.md new file mode 100644 index 00000000..32f8e50d --- /dev/null +++ b/docs/framework/preact/reference/functions/useStoreActions.md @@ -0,0 +1,44 @@ +--- +id: useStoreActions +title: useStoreActions +--- + +# Function: useStoreActions() + +```ts +function useStoreActions(store): TActions; +``` + +Defined in: preact-store/src/useStoreActions.ts:16 + +Returns the stable actions bag from a writable store created with actions. + +Use this when a component only needs to call store actions and should not +subscribe to store state. + +## Type Parameters + +### TValue + +`TValue` + +### TActions + +`TActions` *extends* `StoreActionMap` + +## Parameters + +### store + +`Store`\<`TValue`, `TActions`\> + +## Returns + +`TActions` + +## Example + +```tsx +const actions = useStoreActions(counterStore) +actions.increment() +``` diff --git a/docs/framework/preact/reference/functions/useValue.md b/docs/framework/preact/reference/functions/useValue.md new file mode 100644 index 00000000..7c1e8224 --- /dev/null +++ b/docs/framework/preact/reference/functions/useValue.md @@ -0,0 +1,47 @@ +--- +id: useValue +title: useValue +--- + +# Function: useValue() + +```ts +function useValue(source, options?): TValue; +``` + +Defined in: preact-store/src/useValue.ts:22 + +Subscribes to an atom or store and returns its current value. + +This is the whole-value counterpart to [useSelector](useSelector.md). Use it when the +component needs the entire current value from a source. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +`TValue` + +## Examples + +```tsx +const count = useValue(countAtom) +``` + +```tsx +const state = useValue(counterStore) +``` diff --git a/docs/framework/preact/reference/index.md b/docs/framework/preact/reference/index.md index 1ef55ff2..e243ae83 100644 --- a/docs/framework/preact/reference/index.md +++ b/docs/framework/preact/reference/index.md @@ -5,7 +5,19 @@ title: "@tanstack/preact-store" # @tanstack/preact-store +## Interfaces + +- [UseSelectorOptions](interfaces/UseSelectorOptions.md) + ## Functions +- [createStoreContext](functions/createStoreContext.md) - [shallow](functions/shallow.md) -- [useStore](functions/useStore.md) +- [useAtom](functions/useAtom.md) +- [useCreateAtom](functions/useCreateAtom.md) +- [useCreateStore](functions/useCreateStore.md) +- [useSelector](functions/useSelector.md) +- [useSetValue](functions/useSetValue.md) +- [~~useStore~~](functions/useStore.md) +- [useStoreActions](functions/useStoreActions.md) +- [useValue](functions/useValue.md) diff --git a/docs/framework/preact/reference/interfaces/UseSelectorOptions.md b/docs/framework/preact/reference/interfaces/UseSelectorOptions.md new file mode 100644 index 00000000..f70163cf --- /dev/null +++ b/docs/framework/preact/reference/interfaces/UseSelectorOptions.md @@ -0,0 +1,38 @@ +--- +id: UseSelectorOptions +title: UseSelectorOptions +--- + +# Interface: UseSelectorOptions\ + +Defined in: preact-store/src/useSelector.ts:9 + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: preact-store/src/useSelector.ts:10 + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` diff --git a/docs/framework/react/reference/functions/useStore.md b/docs/framework/react/reference/functions/useStore.md index 6eaf58bb..7fab183d 100644 --- a/docs/framework/react/reference/functions/useStore.md +++ b/docs/framework/react/reference/functions/useStore.md @@ -12,7 +12,7 @@ function useStore( compare?): TSelected; ``` -Defined in: [packages/react-store/src/useStore.ts:8](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStore.ts#L8) +Defined in: [packages/react-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStore.ts#L13) Deprecated alias for [useSelector](useSelector.md). @@ -50,6 +50,12 @@ Deprecated alias for [useSelector](useSelector.md). `TSelected` +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + ## Deprecated Use `useSelector` instead. diff --git a/docs/framework/solid/quick-start.md b/docs/framework/solid/quick-start.md index 67819d76..19f63861 100644 --- a/docs/framework/solid/quick-start.md +++ b/docs/framework/solid/quick-start.md @@ -6,7 +6,7 @@ id: quick-start The basic Solid app example to get started with the TanStack Solid-store. ```jsx -import { createStore, useStore } from '@tanstack/solid-store'; +import { createStore, useSelector } from '@tanstack/solid-store'; // You can instantiate the store outside of Solid components too! export const store = createStore({ @@ -15,7 +15,7 @@ export const store = createStore({ }) export const Display = (props) => { -  const count = useStore(store, (state) => state[props.animals]); +  const count = useSelector(store, (state) => state[props.animals]);   return (           {props.animals}: {count()} @@ -58,3 +58,5 @@ const App = () => { export default App; ``` + +`useStore` remains available as a deprecated alias to `useSelector`. diff --git a/docs/framework/solid/reference/functions/shallow.md b/docs/framework/solid/reference/functions/shallow.md index d8809f33..eebc35dc 100644 --- a/docs/framework/solid/reference/functions/shallow.md +++ b/docs/framework/solid/reference/functions/shallow.md @@ -9,7 +9,7 @@ title: shallow function shallow(objA, objB): boolean; ``` -Defined in: [index.tsx:35](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L35) +Defined in: solid-store/src/shallow.ts:1 ## Type Parameters diff --git a/docs/framework/solid/reference/functions/useAtom.md b/docs/framework/solid/reference/functions/useAtom.md new file mode 100644 index 00000000..ffc97fee --- /dev/null +++ b/docs/framework/solid/reference/functions/useAtom.md @@ -0,0 +1,49 @@ +--- +id: useAtom +title: useAtom +--- + +# Function: useAtom() + +```ts +function useAtom(atom, options?): [Accessor, (fn) => void & (value) => void]; +``` + +Defined in: solid-store/src/useAtom.ts:24 + +Returns the current atom accessor together with a setter. + +Use this when a component needs to both read and update the same writable +atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +\[`Accessor`\<`TValue`\>, (`fn`) => `void` & (`value`) => `void`\] + +## Example + +```tsx +const [count, setCount] = useAtom(countAtom) + +return ( + +) +``` diff --git a/docs/framework/solid/reference/functions/useSelector.md b/docs/framework/solid/reference/functions/useSelector.md new file mode 100644 index 00000000..fecd9f0f --- /dev/null +++ b/docs/framework/solid/reference/functions/useSelector.md @@ -0,0 +1,61 @@ +--- +id: useSelector +title: useSelector +--- + +# Function: useSelector() + +```ts +function useSelector( + source, + selector, +options?): Accessor; +``` + +Defined in: solid-store/src/useSelector.ts:38 + +Selects a slice of state from an atom or store and subscribes the component +to that selection. + +This is the primary Solid read hook for TanStack Store. It returns a Solid +accessor so consumers can read the selected value reactively. + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +`SelectionSource`\<`TSource`\> + +### selector + +(`snapshot`) => `TSelected` + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> + +## Returns + +`Accessor`\<`TSelected`\> + +## Examples + +```tsx +const count = useSelector(counterStore, (state) => state.count) + +return

{count()}

+``` + +```tsx +const doubled = useSelector(countAtom, (value) => value * 2) +``` diff --git a/docs/framework/solid/reference/functions/useSetValue.md b/docs/framework/solid/reference/functions/useSetValue.md new file mode 100644 index 00000000..7c206203 --- /dev/null +++ b/docs/framework/solid/reference/functions/useSetValue.md @@ -0,0 +1,104 @@ +--- +id: useSetValue +title: useSetValue +--- + +# Function: useSetValue() + +## Call Signature + +```ts +function useSetValue(source): (fn) => void & (value) => void; +``` + +Defined in: solid-store/src/useSetValue.ts:21 + +Returns a stable setter for a writable atom or store. + +Writable atoms preserve their native `set` contract. Writable stores +preserve their native `setState` contract. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### source + +`Atom`\<`TValue`\> + +### Returns + +(`fn`) => `void` & (`value`) => `void` + +### Examples + +```tsx +const setCount = useSetValue(countAtom) +setCount((prev) => prev + 1) +``` + +```tsx +const setState = useSetValue(counterStore) +setState((state) => ({ ...state, count: state.count + 1 })) +``` + +## Call Signature + +```ts +function useSetValue(source): (updater) => void; +``` + +Defined in: solid-store/src/useSetValue.ts:22 + +Returns a stable setter for a writable atom or store. + +Writable atoms preserve their native `set` contract. Writable stores +preserve their native `setState` contract. + +### Type Parameters + +#### TValue + +`TValue` + +#### TActions + +`TActions` *extends* `StoreActionMap` + +### Parameters + +#### source + +`Store`\<`TValue`, `TActions`\> + +### Returns + +```ts +(updater): void; +``` + +#### Parameters + +##### updater + +(`prev`) => `TValue` + +#### Returns + +`void` + +### Examples + +```tsx +const setCount = useSetValue(countAtom) +setCount((prev) => prev + 1) +``` + +```tsx +const setState = useSetValue(counterStore) +setState((state) => ({ ...state, count: state.count + 1 })) +``` diff --git a/docs/framework/solid/reference/functions/useStore.md b/docs/framework/solid/reference/functions/useStore.md index 6952ba19..403da190 100644 --- a/docs/framework/solid/reference/functions/useStore.md +++ b/docs/framework/solid/reference/functions/useStore.md @@ -3,41 +3,59 @@ id: useStore title: useStore --- -# Function: useStore() +# ~~Function: useStore()~~ ```ts -function useStore( - store, +function useStore( + source, selector, -options): Accessor; +compare?): Accessor; ``` -Defined in: [index.tsx:12](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L12) +Defined in: solid-store/src/useStore.ts:14 + +Deprecated alias for [useSelector](useSelector.md). ## Type Parameters -### TState +### TSource -`TState` +`TSource` ### TSelected -`TSelected` = `NoInfer`\<`TState`\> +`TSelected` ## Parameters -### store +### source + +#### get + +() => `TSource` -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> +#### subscribe + +(`listener`) => `object` ### selector -(`state`) => `TSelected` +(`snapshot`) => `TSelected` -### options +### compare? -`UseStoreOptions`\<`TSelected`\> = `{}` +(`a`, `b`) => `boolean` ## Returns `Accessor`\<`TSelected`\> + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/solid/reference/functions/useStoreActions.md b/docs/framework/solid/reference/functions/useStoreActions.md new file mode 100644 index 00000000..64a7c1b8 --- /dev/null +++ b/docs/framework/solid/reference/functions/useStoreActions.md @@ -0,0 +1,44 @@ +--- +id: useStoreActions +title: useStoreActions +--- + +# Function: useStoreActions() + +```ts +function useStoreActions(store): TActions; +``` + +Defined in: solid-store/src/useStoreActions.ts:15 + +Returns the stable actions bag from a writable store created with actions. + +Use this when a component only needs to call store actions and should not +subscribe to store state. + +## Type Parameters + +### TValue + +`TValue` + +### TActions + +`TActions` *extends* `StoreActionMap` + +## Parameters + +### store + +`Store`\<`TValue`, `TActions`\> + +## Returns + +`TActions` + +## Example + +```tsx +const actions = useStoreActions(counterStore) +actions.increment() +``` diff --git a/docs/framework/solid/reference/functions/useValue.md b/docs/framework/solid/reference/functions/useValue.md new file mode 100644 index 00000000..3eec9903 --- /dev/null +++ b/docs/framework/solid/reference/functions/useValue.md @@ -0,0 +1,49 @@ +--- +id: useValue +title: useValue +--- + +# Function: useValue() + +```ts +function useValue(source, options?): Accessor; +``` + +Defined in: solid-store/src/useValue.ts:24 + +Subscribes to an atom or store and returns its current value accessor. + +This is the whole-value counterpart to [useSelector](useSelector.md). Use it when the +component needs the entire current value from a source. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +`Accessor`\<`TValue`\> + +## Examples + +```tsx +const count = useValue(countAtom) + +return

{count()}

+``` + +```tsx +const state = useValue(counterStore) +``` diff --git a/docs/framework/solid/reference/index.md b/docs/framework/solid/reference/index.md index 71951743..b3b2c81c 100644 --- a/docs/framework/solid/reference/index.md +++ b/docs/framework/solid/reference/index.md @@ -5,7 +5,16 @@ title: "@tanstack/solid-store" # @tanstack/solid-store +## Interfaces + +- [UseSelectorOptions](interfaces/UseSelectorOptions.md) + ## Functions - [shallow](functions/shallow.md) -- [useStore](functions/useStore.md) +- [useAtom](functions/useAtom.md) +- [useSelector](functions/useSelector.md) +- [useSetValue](functions/useSetValue.md) +- [~~useStore~~](functions/useStore.md) +- [useStoreActions](functions/useStoreActions.md) +- [useValue](functions/useValue.md) diff --git a/docs/framework/solid/reference/interfaces/UseSelectorOptions.md b/docs/framework/solid/reference/interfaces/UseSelectorOptions.md new file mode 100644 index 00000000..f5f2ec6a --- /dev/null +++ b/docs/framework/solid/reference/interfaces/UseSelectorOptions.md @@ -0,0 +1,38 @@ +--- +id: UseSelectorOptions +title: UseSelectorOptions +--- + +# Interface: UseSelectorOptions\ + +Defined in: solid-store/src/useSelector.ts:4 + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: solid-store/src/useSelector.ts:5 + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` diff --git a/docs/framework/svelte/quick-start.md b/docs/framework/svelte/quick-start.md index 3c126fb2..5fe7d502 100644 --- a/docs/framework/svelte/quick-start.md +++ b/docs/framework/svelte/quick-start.md @@ -45,17 +45,19 @@ export function updateState(animal: 'cats' | 'dogs') { **Display.svelte** ```html
{ animal }: { count.current }
``` +`useStore` remains available as a deprecated alias to `useSelector`. + **Increment.svelte** ```html @@ -58,6 +58,8 @@ const count = useStore(store, (state) => state[props.animal]); ``` +`useStore` remains available as a deprecated alias to `useSelector`. + **Increment.vue** ```vue + + diff --git a/examples/preact/atoms/package.json b/examples/preact/atoms/package.json new file mode 100644 index 00000000..16e05d54 --- /dev/null +++ b/examples/preact/atoms/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/atoms/src/index.tsx b/examples/preact/atoms/src/index.tsx new file mode 100644 index 00000000..4563f64a --- /dev/null +++ b/examples/preact/atoms/src/index.tsx @@ -0,0 +1,65 @@ +import { render } from 'preact' +import { + createAtom, + useAtom, + // useCreateAtom, + useSetValue, + useValue, +} from '@tanstack/preact-store' + +// Optionally, you can create atoms outside of Preact components at module scope +const countAtom = createAtom(0) + +function App() { + // or define atoms inside of components with hook variant. You would have to pass atom as props or use store context though. + // const countAtom = useCreateAtom(0) + + return ( +
+

Preact Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with + the Preact hooks. +

+ + + +
+ ) +} + +function AtomValuePanel() { + const count = useValue(countAtom) // useValue re-renders when the value changes. Useful for read-only access to an atom. + + return

Total: {count}

+} + +function AtomButtons() { + const setCount = useSetValue(countAtom) // useSetValue never causes a re-render (useAtom does) if you need write-only in a component + + return ( +
+ + +
+ ) +} + +function AtomStepper() { + const [count, setCount] = useAtom(countAtom) // read and write access to the atom. Re-renders when the value changes. + + return ( +
+

Editable count: {count}

+ +
+ ) +} + +render(, document.getElementById('app')!) diff --git a/examples/preact/atoms/tsconfig.json b/examples/preact/atoms/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/atoms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/atoms/vite.config.ts b/examples/preact/atoms/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/atoms/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/preact/simple/README.md b/examples/preact/simple/README.md index a9d90bf0..a29e7193 100644 --- a/examples/preact/simple/README.md +++ b/examples/preact/simple/README.md @@ -1,15 +1,6 @@ -# `create-preact` +# Preact Simple Example -

- -

+To run this example: -

Get started using Preact and Vite!

- -## Getting Started - -- `pnpm dev` - Starts a dev server at http://localhost:5173/ - -- `pnpm build` - Builds for production, emitting to `dist/` - -- `pnpm preview` - Starts a server at http://localhost:4173/ to test production build locally +- `npm install` +- `npm run dev` diff --git a/examples/preact/simple/src/index.tsx b/examples/preact/simple/src/index.tsx index ffc07839..09609b6d 100644 --- a/examples/preact/simple/src/index.tsx +++ b/examples/preact/simple/src/index.tsx @@ -1,21 +1,53 @@ import { render } from 'preact' -import { Store, useStore } from '@tanstack/preact-store' +import { Store, useSelector } from '@tanstack/preact-store' +// You can instantiate a Store outside of Preact components too! export const store = new Store({ - count: 0, + dogs: 0, + cats: 0, }) -function Counter() { - const count = useStore(store, (state) => state.count) +interface DisplayProps { + animal: 'dogs' | 'cats' +} + +// This will only re-render when `state[animal]` changes. If an unrelated store property changes, it won't re-render +function Display({ animal }: DisplayProps) { + const count = useSelector(store, (state) => state[animal]) // formerly, useStore. Now renamed to useSelector. + return
{`${animal}: ${count}`}
+} + +const updateState = (animal: 'dogs' | 'cats') => { + store.setState((state) => { + return { + ...state, + [animal]: state[animal] + 1, + } + }) +} + +interface IncrementProps { + animal: 'dogs' | 'cats' +} + +const Increment = ({ animal }: IncrementProps) => ( + +) + +function App() { return (
-
Count: {count}
- +

How many of your friends like cats or dogs?

+

+ Press one of the buttons to add a counter of how many of your friends + like cats or dogs +

+ + + +
) } -const root = document.body -render(, root) +render(, document.getElementById('app')!) diff --git a/examples/preact/simple/tsconfig.json b/examples/preact/simple/tsconfig.json index b8f664db..bd8198e7 100644 --- a/examples/preact/simple/tsconfig.json +++ b/examples/preact/simple/tsconfig.json @@ -13,5 +13,6 @@ "jsxImportSource": "preact", "skipLibCheck": true }, - "include": ["node_modules/vite/client.d.ts", "**/*"] + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] } diff --git a/examples/preact/store-actions/README.md b/examples/preact/store-actions/README.md new file mode 100644 index 00000000..8b5588fd --- /dev/null +++ b/examples/preact/store-actions/README.md @@ -0,0 +1,12 @@ +# Preact Store Actions Example + +This example demonstrates: + +- `useSelector` +- `useStoreActions` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/preact/store-actions/index.html b/examples/preact/store-actions/index.html new file mode 100644 index 00000000..b9bd38f8 --- /dev/null +++ b/examples/preact/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Preact Store Actions Example App + + +
+ + + diff --git a/examples/preact/store-actions/package.json b/examples/preact/store-actions/package.json new file mode 100644 index 00000000..9c6b3bd1 --- /dev/null +++ b/examples/preact/store-actions/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/store-actions/src/index.tsx b/examples/preact/store-actions/src/index.tsx new file mode 100644 index 00000000..3cd0f56b --- /dev/null +++ b/examples/preact/store-actions/src/index.tsx @@ -0,0 +1,81 @@ +import { render } from 'preact' +import { Store, useSelector, useStoreActions } from '@tanstack/preact-store' + +// Optionally, you can create stores outside of Preact components at module scope +const petStore = new Store( + { + cats: 0, + dogs: 0, + }, + ({ set }) => + // optionally, define actions for updating your store in specific ways right on the store. + ({ + addCat: () => + set((prev) => ({ + ...prev, + cats: prev.cats + 1, + })), + addDog: () => + set((prev) => ({ + ...prev, + dogs: prev.dogs + 1, + })), + }), +) + +function App() { + // or define stores inside of components with hook variant. You would have to pass store as props or use store context though. + // const petStore = useCreateStore(...) + + return ( +
+

Preact Store Actions

+

+ This example creates a module-level store with actions. Components read + state with `useSelector` and call mutations through `useStoreActions`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function StoreButtons() { + // pull stable action functions from the store + const { addCat, addDog } = useStoreActions(petStore) + + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +render(, document.getElementById('app')!) diff --git a/examples/preact/store-actions/tsconfig.json b/examples/preact/store-actions/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/store-actions/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/store-actions/vite.config.ts b/examples/preact/store-actions/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/store-actions/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/preact/store-context/README.md b/examples/preact/store-context/README.md new file mode 100644 index 00000000..c90e4891 --- /dev/null +++ b/examples/preact/store-context/README.md @@ -0,0 +1,17 @@ +# Preact Store Context Example + +This example demonstrates: + +- `createStoreContext` +- `useCreateStore` +- `useCreateAtom` +- `useStoreContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/preact/store-context/index.html b/examples/preact/store-context/index.html new file mode 100644 index 00000000..099280fe --- /dev/null +++ b/examples/preact/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Preact Store Context Example App + + +
+ + + diff --git a/examples/preact/store-context/package.json b/examples/preact/store-context/package.json new file mode 100644 index 00000000..aefc8aa3 --- /dev/null +++ b/examples/preact/store-context/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/store-context/src/index.tsx b/examples/preact/store-context/src/index.tsx new file mode 100644 index 00000000..de148d0b --- /dev/null +++ b/examples/preact/store-context/src/index.tsx @@ -0,0 +1,160 @@ +import { render } from 'preact' +import { + useAtom, + useCreateAtom, + createStoreContext, + useCreateStore, + useSelector, + useSetValue, + useValue, +} from '@tanstack/preact-store' +import type { Atom, Store } from '@tanstack/preact-store' + +// one drawback of storing stores and atoms in context is you have to define types for the context manually, instead of everything being inferred. + +type CounterStore = { + cats: number + dogs: number +} + +type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +// create context provider and hook +const { StoreProvider, useStoreContext } = + createStoreContext() + +// top-level app component with provider +function App() { + // create the store + const votesStore = useCreateStore({ + cats: 0, + dogs: 0, + }) + // create the atom + const countAtom = useCreateAtom(0) + + return ( + // provide both the store and atom in a single context object + +
+

Preact Store Context

+

+ This example provides both atoms and stores through a single typed + context object, then consumes them from nested components. +

+ + + + +
+

Nested Atom Components

+ + + +
+
+
+ ) +} + +function CatCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function TotalCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // custom selector to calculate total votes from the store state + const total = useSelector(votesStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +function AtomSummary() { + // pull an atom from context + const { countAtom } = useStoreContext() + const count = useValue(countAtom) + + return

Atom count: {count}

+} + +function NestedAtomControls() { + const { countAtom } = useStoreContext() + const setCount = useSetValue(countAtom) + + return ( +
+ + +
+ ) +} + +function DeepAtomEditor() { + const { countAtom } = useStoreContext() + const [count, setCount] = useAtom(countAtom) + + return ( +
+

Editable atom count: {count}

+ +
+ ) +} + +function StoreButtons() { + const { votesStore } = useStoreContext() + const setVotes = useSetValue(votesStore) + + return ( +
+ + +
+ ) +} + +render(, document.getElementById('app')!) diff --git a/examples/preact/store-context/tsconfig.json b/examples/preact/store-context/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/store-context/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/store-context/vite.config.ts b/examples/preact/store-context/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/store-context/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/preact/stores/README.md b/examples/preact/stores/README.md new file mode 100644 index 00000000..c4e36d67 --- /dev/null +++ b/examples/preact/stores/README.md @@ -0,0 +1,12 @@ +# Preact Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/preact/stores/index.html b/examples/preact/stores/index.html new file mode 100644 index 00000000..0a729bff --- /dev/null +++ b/examples/preact/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Preact Stores Example App + + +
+ + + diff --git a/examples/preact/stores/package.json b/examples/preact/stores/package.json new file mode 100644 index 00000000..efedc217 --- /dev/null +++ b/examples/preact/stores/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/stores/src/index.tsx b/examples/preact/stores/src/index.tsx new file mode 100644 index 00000000..f39190ca --- /dev/null +++ b/examples/preact/stores/src/index.tsx @@ -0,0 +1,79 @@ +import { render } from 'preact' +import { Store, useSelector } from '@tanstack/preact-store' + +// Optionally, you can create stores outside of Preact components at module scope +const petStore = new Store({ + cats: 0, + dogs: 0, +}) + +function App() { + // or define stores inside of components with hook variant. You would have to pass store as props or use store context though. + // const petStore = useCreateStore(...) + + return ( +
+

Preact Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function StoreButtons() { + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +render(, document.getElementById('app')!) diff --git a/examples/preact/stores/tsconfig.json b/examples/preact/stores/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/stores/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/stores/vite.config.ts b/examples/preact/stores/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/stores/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/solid/atoms/README.md b/examples/solid/atoms/README.md new file mode 100644 index 00000000..bbe7bfe8 --- /dev/null +++ b/examples/solid/atoms/README.md @@ -0,0 +1,12 @@ +# Solid Atom Hooks Example + +This example demonstrates: + +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/atoms/index.html b/examples/solid/atoms/index.html new file mode 100644 index 00000000..829c3d75 --- /dev/null +++ b/examples/solid/atoms/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Atoms Example App + + +
+ + + diff --git a/examples/solid/atoms/package.json b/examples/solid/atoms/package.json new file mode 100644 index 00000000..0059230f --- /dev/null +++ b/examples/solid/atoms/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/atoms/src/index.tsx b/examples/solid/atoms/src/index.tsx new file mode 100644 index 00000000..c9ab76bb --- /dev/null +++ b/examples/solid/atoms/src/index.tsx @@ -0,0 +1,61 @@ +import { render } from 'solid-js/web' +import { + createAtom, + useAtom, + useSetValue, + useValue, +} from '@tanstack/solid-store' + +// Optionally, you can create atoms outside of Solid components at module scope +const countAtom = createAtom(0) + +function App() { + return ( +
+

Solid Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with + the Solid hooks. +

+ + + +
+ ) +} + +function AtomValuePanel() { + const count = useValue(countAtom) // useValue re-renders when the value changes. Useful for read-only access to an atom. + + return

Total: {count()}

+} + +function AtomButtons() { + const setCount = useSetValue(countAtom) // useSetValue avoids wiring the current value into this component when you only need writes. + + return ( +
+ + +
+ ) +} + +function AtomStepper() { + const [count, setCount] = useAtom(countAtom) // read and write access to the atom. + + return ( +
+

Editable count: {count()}

+ +
+ ) +} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/atoms/tsconfig.json b/examples/solid/atoms/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/atoms/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/atoms/vite.config.ts b/examples/solid/atoms/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/atoms/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/solid/simple/README.md b/examples/solid/simple/README.md index 99613fc0..f34d525e 100644 --- a/examples/solid/simple/README.md +++ b/examples/solid/simple/README.md @@ -1,28 +1,6 @@ -## Usage +# Solid Simple Example -```bash -$ npm install # or pnpm install or yarn install -``` +To run this example: -### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -Runs the app in the development mode.
-Open [http://localhost:5173](http://localhost:5173) to view it in the browser. - -### `npm run build` - -Builds the app for production to the `dist` folder.
-It correctly bundles Solid in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -## Deployment - -Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html) +- `npm install` +- `npm run dev` diff --git a/examples/solid/simple/src/index.tsx b/examples/solid/simple/src/index.tsx index c99f311b..97a4355c 100644 --- a/examples/solid/simple/src/index.tsx +++ b/examples/solid/simple/src/index.tsx @@ -1,4 +1,4 @@ -import { Store, useStore } from '@tanstack/solid-store' +import { Store, useSelector } from '@tanstack/solid-store' import { render } from 'solid-js/web' // You can instantiate a Store outside of Solid components too! @@ -12,7 +12,7 @@ interface DisplayProps { } export const Display = (props: DisplayProps) => { - const count = useStore(store, (state) => state[props.animals]) + const count = useSelector(store, (state) => state[props.animals]) // formerly, useStore. Now renamed to useSelector. return (
{props.animals}: {count()} diff --git a/examples/solid/store-actions/README.md b/examples/solid/store-actions/README.md new file mode 100644 index 00000000..690a3dbc --- /dev/null +++ b/examples/solid/store-actions/README.md @@ -0,0 +1,12 @@ +# Solid Store Actions Example + +This example demonstrates: + +- `useSelector` +- `useStoreActions` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/store-actions/index.html b/examples/solid/store-actions/index.html new file mode 100644 index 00000000..6d560e19 --- /dev/null +++ b/examples/solid/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Store Actions Example App + + +
+ + + diff --git a/examples/solid/store-actions/package.json b/examples/solid/store-actions/package.json new file mode 100644 index 00000000..454334b3 --- /dev/null +++ b/examples/solid/store-actions/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/store-actions/src/index.tsx b/examples/solid/store-actions/src/index.tsx new file mode 100644 index 00000000..d5597a38 --- /dev/null +++ b/examples/solid/store-actions/src/index.tsx @@ -0,0 +1,78 @@ +import { render } from 'solid-js/web' +import { Store, useSelector, useStoreActions } from '@tanstack/solid-store' + +// Optionally, you can create stores outside of Solid components at module scope +const petStore = new Store( + { + cats: 0, + dogs: 0, + }, + ({ set }) => + // optionally, define actions for updating your store in specific ways right on the store. + ({ + addCat: () => + set((prev) => ({ + ...prev, + cats: prev.cats + 1, + })), + addDog: () => + set((prev) => ({ + ...prev, + dogs: prev.dogs + 1, + })), + }), +) + +function App() { + return ( +
+

Solid Store Actions

+

+ This example creates a module-level store with actions. Components read + state with `useSelector` and call mutations through `useStoreActions`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value()}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value()}

+} + +function StoreButtons() { + // pull stable action functions from the store + const { addCat, addDog } = useStoreActions(petStore) + + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total()}

+} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/store-actions/tsconfig.json b/examples/solid/store-actions/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/store-actions/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/store-actions/vite.config.ts b/examples/solid/store-actions/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/store-actions/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/solid/store-context/README.md b/examples/solid/store-context/README.md new file mode 100644 index 00000000..8ea54a87 --- /dev/null +++ b/examples/solid/store-context/README.md @@ -0,0 +1,14 @@ +# Solid Store Context Example + +This example demonstrates: + +- Solid `createContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/store-context/index.html b/examples/solid/store-context/index.html new file mode 100644 index 00000000..8d3656ea --- /dev/null +++ b/examples/solid/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Store Context Example App + + +
+ + + diff --git a/examples/solid/store-context/package.json b/examples/solid/store-context/package.json new file mode 100644 index 00000000..6475f866 --- /dev/null +++ b/examples/solid/store-context/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/store-context/src/index.tsx b/examples/solid/store-context/src/index.tsx new file mode 100644 index 00000000..9ab04826 --- /dev/null +++ b/examples/solid/store-context/src/index.tsx @@ -0,0 +1,165 @@ +import { createContext, useContext } from 'solid-js' +import { render } from 'solid-js/web' +import { + createAtom, + Store, + useAtom, + useSelector, + useSetValue, + useValue, +} from '@tanstack/solid-store' +import type { Atom } from '@tanstack/solid-store' + +// one drawback of storing stores and atoms in context is you have to define types for the context manually, instead of everything being inferred. + +type CounterStore = { + cats: number + dogs: number +} + +type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +const StoreContext = createContext() + +function useStoreContext() { + const value = useContext(StoreContext) + + if (!value) { + throw new Error('Missing StoreProvider for StoreContext') + } + + return value +} + +function App() { + // Solid components only run once per mount, so stores and atoms created here stay stable for this provider instance. + const votesStore = new Store({ + cats: 0, + dogs: 0, + }) + const countAtom = createAtom(0) + + return ( + +
+

Solid Store Context

+

+ This example provides both atoms and stores through a single typed + context object, then consumes them from nested components. +

+ + + + +
+

Nested Atom Components

+ + + +
+
+
+ ) +} + +function CatCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.cats) + + return

Cats: {value()}

+} + +function DogCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.dogs) + + return

Dogs: {value()}

+} + +function TotalCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // custom selector to calculate total votes from the store state + const total = useSelector(votesStore, (state) => state.cats + state.dogs) + + return

Total votes: {total()}

+} + +function AtomSummary() { + // pull an atom from context + const { countAtom } = useStoreContext() + const count = useValue(countAtom) + + return

Atom count: {count()}

+} + +function NestedAtomControls() { + const { countAtom } = useStoreContext() + const setCount = useSetValue(countAtom) + + return ( +
+ + +
+ ) +} + +function DeepAtomEditor() { + const { countAtom } = useStoreContext() + const [count, setCount] = useAtom(countAtom) + + return ( +
+

Editable atom count: {count()}

+ +
+ ) +} + +function StoreButtons() { + const { votesStore } = useStoreContext() + const setVotes = useSetValue(votesStore) + + return ( +
+ + +
+ ) +} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/store-context/tsconfig.json b/examples/solid/store-context/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/store-context/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/store-context/vite.config.ts b/examples/solid/store-context/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/solid/stores/README.md b/examples/solid/stores/README.md new file mode 100644 index 00000000..623b48f3 --- /dev/null +++ b/examples/solid/stores/README.md @@ -0,0 +1,12 @@ +# Solid Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/stores/index.html b/examples/solid/stores/index.html new file mode 100644 index 00000000..5eff5b4f --- /dev/null +++ b/examples/solid/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Stores Example App + + +
+ + + diff --git a/examples/solid/stores/package.json b/examples/solid/stores/package.json new file mode 100644 index 00000000..cc97b8d8 --- /dev/null +++ b/examples/solid/stores/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/stores/src/index.tsx b/examples/solid/stores/src/index.tsx new file mode 100644 index 00000000..8f204e65 --- /dev/null +++ b/examples/solid/stores/src/index.tsx @@ -0,0 +1,76 @@ +import { render } from 'solid-js/web' +import { Store, useSelector } from '@tanstack/solid-store' + +// Optionally, you can create stores outside of Solid components at module scope +const petStore = new Store({ + cats: 0, + dogs: 0, +}) + +function App() { + return ( +
+

Solid Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value()}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value()}

+} + +function StoreButtons() { + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total()}

+} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/stores/tsconfig.json b/examples/solid/stores/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/stores/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/stores/vite.config.ts b/examples/solid/stores/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/stores/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/svelte/atoms/README.md b/examples/svelte/atoms/README.md new file mode 100644 index 00000000..8c30f684 --- /dev/null +++ b/examples/svelte/atoms/README.md @@ -0,0 +1,12 @@ +# Svelte Atom Hooks Example + +This example demonstrates: + +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/atoms/index.html b/examples/svelte/atoms/index.html new file mode 100644 index 00000000..7e5b4461 --- /dev/null +++ b/examples/svelte/atoms/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Atoms Example App + + +
+ + + diff --git a/examples/svelte/atoms/package.json b/examples/svelte/atoms/package.json new file mode 100644 index 00000000..1000e895 --- /dev/null +++ b/examples/svelte/atoms/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-atoms", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/atoms/src/App.svelte b/examples/svelte/atoms/src/App.svelte new file mode 100644 index 00000000..03f89b88 --- /dev/null +++ b/examples/svelte/atoms/src/App.svelte @@ -0,0 +1,36 @@ + + +
+

Svelte Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with the + Svelte hooks. +

+

Total: {count.current}

+
+ + +
+
+

Editable count: {editableCount.current}

+ +
+
diff --git a/examples/svelte/atoms/src/main.ts b/examples/svelte/atoms/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/atoms/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/atoms/src/vite-env.d.ts b/examples/svelte/atoms/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/atoms/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/atoms/svelte.config.js b/examples/svelte/atoms/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/atoms/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/atoms/tsconfig.json b/examples/svelte/atoms/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/atoms/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/atoms/tsconfig.node.json b/examples/svelte/atoms/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/atoms/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/atoms/vite.config.ts b/examples/svelte/atoms/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/atoms/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/simple/README.md b/examples/svelte/simple/README.md index e6cd94fc..b4e8e337 100644 --- a/examples/svelte/simple/README.md +++ b/examples/svelte/simple/README.md @@ -1,47 +1,6 @@ -# Svelte + TS + Vite +# Svelte Simple Example -This template should help get you started developing with Svelte and TypeScript in Vite. +To run this example: -## Recommended IDE Setup - -[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). - -## Need an official Svelte framework? - -Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. - -## Technical considerations - -**Why use this over SvelteKit?** - -- It brings its own routing solution which might not be preferable for some users. -- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - -This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. - -Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. - -**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** - -Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. - -**Why include `.vscode/extensions.json`?** - -Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. - -**Why enable `allowJs` in the TS template?** - -While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. - -**Why is HMR not preserving my local component state?** - -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). - -If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. - -```ts -// store.ts -// An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) -``` +- `npm install` +- `npm run dev` diff --git a/examples/svelte/simple/src/Display.svelte b/examples/svelte/simple/src/Display.svelte index b364c925..5d1a48aa 100644 --- a/examples/svelte/simple/src/Display.svelte +++ b/examples/svelte/simple/src/Display.svelte @@ -1,9 +1,9 @@ diff --git a/examples/svelte/store-actions/README.md b/examples/svelte/store-actions/README.md new file mode 100644 index 00000000..128fa117 --- /dev/null +++ b/examples/svelte/store-actions/README.md @@ -0,0 +1,12 @@ +# Svelte Store Actions Example + +This example demonstrates: + +- `useSelector` +- `useStoreActions` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/store-actions/index.html b/examples/svelte/store-actions/index.html new file mode 100644 index 00000000..f9a099cd --- /dev/null +++ b/examples/svelte/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Store Actions Example App + + +
+ + + diff --git a/examples/svelte/store-actions/package.json b/examples/svelte/store-actions/package.json new file mode 100644 index 00000000..9bf1ffbc --- /dev/null +++ b/examples/svelte/store-actions/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-store-actions", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/store-actions/src/App.svelte b/examples/svelte/store-actions/src/App.svelte new file mode 100644 index 00000000..d44faf06 --- /dev/null +++ b/examples/svelte/store-actions/src/App.svelte @@ -0,0 +1,47 @@ + + +
+

Svelte Store Actions

+

+ This example creates a module-level store with actions. Components read + state with `useSelector` and call mutations through `useStoreActions`. +

+

Cats: {cats.current}

+

Dogs: {dogs.current}

+

Total votes: {total.current}

+
+ + +
+
diff --git a/examples/svelte/store-actions/src/main.ts b/examples/svelte/store-actions/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/store-actions/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/store-actions/src/vite-env.d.ts b/examples/svelte/store-actions/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/store-actions/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/store-actions/svelte.config.js b/examples/svelte/store-actions/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/store-actions/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/store-actions/tsconfig.json b/examples/svelte/store-actions/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/store-actions/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/store-actions/tsconfig.node.json b/examples/svelte/store-actions/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/store-actions/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/store-actions/vite.config.ts b/examples/svelte/store-actions/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/store-actions/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/store-context/README.md b/examples/svelte/store-context/README.md new file mode 100644 index 00000000..e43aec87 --- /dev/null +++ b/examples/svelte/store-context/README.md @@ -0,0 +1,14 @@ +# Svelte Store Context Example + +This example demonstrates: + +- Svelte `setContext`/`getContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/store-context/index.html b/examples/svelte/store-context/index.html new file mode 100644 index 00000000..01da3cc3 --- /dev/null +++ b/examples/svelte/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Store Context Example App + + +
+ + + diff --git a/examples/svelte/store-context/package.json b/examples/svelte/store-context/package.json new file mode 100644 index 00000000..e5e50572 --- /dev/null +++ b/examples/svelte/store-context/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-store-context", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/store-context/src/App.svelte b/examples/svelte/store-context/src/App.svelte new file mode 100644 index 00000000..0df2155b --- /dev/null +++ b/examples/svelte/store-context/src/App.svelte @@ -0,0 +1,29 @@ + + +
+

Svelte Store Context

+

+ This example provides both atoms and stores through a single typed context + object, then consumes them from nested components. +

+ + +
diff --git a/examples/svelte/store-context/src/AtomSection.svelte b/examples/svelte/store-context/src/AtomSection.svelte new file mode 100644 index 00000000..392dd9c3 --- /dev/null +++ b/examples/svelte/store-context/src/AtomSection.svelte @@ -0,0 +1,27 @@ + + +
+

Nested Atom Components

+

Atom count: {count.current}

+
+ + +
+
+

Editable atom count: {editableCount.current}

+ +
+
diff --git a/examples/svelte/store-context/src/StoreSection.svelte b/examples/svelte/store-context/src/StoreSection.svelte new file mode 100644 index 00000000..484b0550 --- /dev/null +++ b/examples/svelte/store-context/src/StoreSection.svelte @@ -0,0 +1,39 @@ + + +

Cats: {cats.current}

+

Dogs: {dogs.current}

+

Total votes: {total.current}

+
+ + +
diff --git a/examples/svelte/store-context/src/context.ts b/examples/svelte/store-context/src/context.ts new file mode 100644 index 00000000..0d15dab0 --- /dev/null +++ b/examples/svelte/store-context/src/context.ts @@ -0,0 +1,13 @@ +import type { Atom, Store } from '@tanstack/svelte-store' + +export type CounterStore = { + cats: number + dogs: number +} + +export type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +export const STORE_CONTEXT = Symbol('store-context') diff --git a/examples/svelte/store-context/src/main.ts b/examples/svelte/store-context/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/store-context/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/store-context/src/vite-env.d.ts b/examples/svelte/store-context/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/store-context/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/store-context/svelte.config.js b/examples/svelte/store-context/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/store-context/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/store-context/tsconfig.json b/examples/svelte/store-context/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/store-context/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/store-context/tsconfig.node.json b/examples/svelte/store-context/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/store-context/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/store-context/vite.config.ts b/examples/svelte/store-context/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/stores/README.md b/examples/svelte/stores/README.md new file mode 100644 index 00000000..5a2eb4b0 --- /dev/null +++ b/examples/svelte/stores/README.md @@ -0,0 +1,12 @@ +# Svelte Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/stores/index.html b/examples/svelte/stores/index.html new file mode 100644 index 00000000..1e30df07 --- /dev/null +++ b/examples/svelte/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Stores Example App + + +
+ + + diff --git a/examples/svelte/stores/package.json b/examples/svelte/stores/package.json new file mode 100644 index 00000000..bda27b0f --- /dev/null +++ b/examples/svelte/stores/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-stores", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/stores/src/App.svelte b/examples/svelte/stores/src/App.svelte new file mode 100644 index 00000000..5e3c6606 --- /dev/null +++ b/examples/svelte/stores/src/App.svelte @@ -0,0 +1,44 @@ + + +
+

Svelte Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+

Cats: {cats.current}

+

Dogs: {dogs.current}

+

Total votes: {total.current}

+
+ + +
+
diff --git a/examples/svelte/stores/src/main.ts b/examples/svelte/stores/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/stores/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/stores/src/vite-env.d.ts b/examples/svelte/stores/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/stores/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/stores/svelte.config.js b/examples/svelte/stores/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/stores/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/stores/tsconfig.json b/examples/svelte/stores/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/stores/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/stores/tsconfig.node.json b/examples/svelte/stores/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/stores/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/stores/vite.config.ts b/examples/svelte/stores/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/stores/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/vue/atoms/README.md b/examples/vue/atoms/README.md new file mode 100644 index 00000000..522cac99 --- /dev/null +++ b/examples/vue/atoms/README.md @@ -0,0 +1,12 @@ +# Vue Atom Hooks Example + +This example demonstrates: + +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/atoms/index.html b/examples/vue/atoms/index.html new file mode 100644 index 00000000..d6750447 --- /dev/null +++ b/examples/vue/atoms/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Atoms Example App + + +
+ + + diff --git a/examples/vue/atoms/package.json b/examples/vue/atoms/package.json new file mode 100644 index 00000000..02b792ee --- /dev/null +++ b/examples/vue/atoms/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/atoms/src/App.vue b/examples/vue/atoms/src/App.vue new file mode 100644 index 00000000..19c2a48a --- /dev/null +++ b/examples/vue/atoms/src/App.vue @@ -0,0 +1,36 @@ + + + diff --git a/examples/vue/atoms/src/main.ts b/examples/vue/atoms/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/atoms/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/atoms/src/shims-vue.d.ts b/examples/vue/atoms/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/atoms/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/atoms/tsconfig.json b/examples/vue/atoms/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/atoms/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/atoms/vite.config.ts b/examples/vue/atoms/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/atoms/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/examples/vue/simple/README.md b/examples/vue/simple/README.md index 28462a4a..4143f8f7 100644 --- a/examples/vue/simple/README.md +++ b/examples/vue/simple/README.md @@ -1,6 +1,6 @@ -# Basic example +# Vue Simple Example To run this example: -- `npm install` or `yarn` or `pnpm i` -- `npm run dev` or `yarn dev` or `pnpm dev` +- `npm install` +- `npm run dev` diff --git a/examples/vue/simple/src/Display.vue b/examples/vue/simple/src/Display.vue index ac211d10..9f95603e 100644 --- a/examples/vue/simple/src/Display.vue +++ b/examples/vue/simple/src/Display.vue @@ -1,9 +1,9 @@ diff --git a/examples/vue/store-actions/README.md b/examples/vue/store-actions/README.md new file mode 100644 index 00000000..f9c74683 --- /dev/null +++ b/examples/vue/store-actions/README.md @@ -0,0 +1,12 @@ +# Vue Store Actions Example + +This example demonstrates: + +- `useSelector` +- `useStoreActions` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/store-actions/index.html b/examples/vue/store-actions/index.html new file mode 100644 index 00000000..5bc92e3a --- /dev/null +++ b/examples/vue/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Store Actions Example App + + +
+ + + diff --git a/examples/vue/store-actions/package.json b/examples/vue/store-actions/package.json new file mode 100644 index 00000000..4448bace --- /dev/null +++ b/examples/vue/store-actions/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/store-actions/src/App.vue b/examples/vue/store-actions/src/App.vue new file mode 100644 index 00000000..b32c02f5 --- /dev/null +++ b/examples/vue/store-actions/src/App.vue @@ -0,0 +1,49 @@ + + + diff --git a/examples/vue/store-actions/src/main.ts b/examples/vue/store-actions/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/store-actions/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/store-actions/src/shims-vue.d.ts b/examples/vue/store-actions/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/store-actions/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/store-actions/tsconfig.json b/examples/vue/store-actions/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/store-actions/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/store-actions/vite.config.ts b/examples/vue/store-actions/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/store-actions/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/examples/vue/store-context/README.md b/examples/vue/store-context/README.md new file mode 100644 index 00000000..6d07f72d --- /dev/null +++ b/examples/vue/store-context/README.md @@ -0,0 +1,14 @@ +# Vue Store Context Example + +This example demonstrates: + +- Vue `provide`/`inject` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/store-context/index.html b/examples/vue/store-context/index.html new file mode 100644 index 00000000..74eaf634 --- /dev/null +++ b/examples/vue/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Store Context Example App + + +
+ + + diff --git a/examples/vue/store-context/package.json b/examples/vue/store-context/package.json new file mode 100644 index 00000000..03e1a0f9 --- /dev/null +++ b/examples/vue/store-context/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/store-context/src/App.vue b/examples/vue/store-context/src/App.vue new file mode 100644 index 00000000..7d53d86f --- /dev/null +++ b/examples/vue/store-context/src/App.vue @@ -0,0 +1,162 @@ + + + diff --git a/examples/vue/store-context/src/main.ts b/examples/vue/store-context/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/store-context/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/store-context/src/shims-vue.d.ts b/examples/vue/store-context/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/store-context/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/store-context/tsconfig.json b/examples/vue/store-context/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/store-context/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/store-context/vite.config.ts b/examples/vue/store-context/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/examples/vue/stores/README.md b/examples/vue/stores/README.md new file mode 100644 index 00000000..66ce8b1f --- /dev/null +++ b/examples/vue/stores/README.md @@ -0,0 +1,12 @@ +# Vue Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/stores/index.html b/examples/vue/stores/index.html new file mode 100644 index 00000000..351679fc --- /dev/null +++ b/examples/vue/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Stores Example App + + +
+ + + diff --git a/examples/vue/stores/package.json b/examples/vue/stores/package.json new file mode 100644 index 00000000..1e814dc2 --- /dev/null +++ b/examples/vue/stores/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/stores/src/App.vue b/examples/vue/stores/src/App.vue new file mode 100644 index 00000000..6949019c --- /dev/null +++ b/examples/vue/stores/src/App.vue @@ -0,0 +1,46 @@ + + + diff --git a/examples/vue/stores/src/main.ts b/examples/vue/stores/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/stores/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/stores/src/shims-vue.d.ts b/examples/vue/stores/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/stores/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/stores/tsconfig.json b/examples/vue/stores/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/stores/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/stores/vite.config.ts b/examples/vue/stores/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/stores/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/packages/angular-store/src/index.ts b/packages/angular-store/src/index.ts index 746bfed2..6f4cebb0 100644 --- a/packages/angular-store/src/index.ts +++ b/packages/angular-store/src/index.ts @@ -6,46 +6,78 @@ import { linkedSignal, runInInjectionContext, } from '@angular/core' -import type { Atom, ReadonlyAtom } from '@tanstack/store' import type { CreateSignalOptions, Signal } from '@angular/core' - -type StoreContext = Record +import type { + Atom, + ReadonlyAtom, + ReadonlyStore, + Store, + StoreActionMap, +} from '@tanstack/store' export * from '@tanstack/store' -export function injectStore>( - store: Atom, - selector?: (state: NoInfer) => TSelected, - options?: CreateSignalOptions & { injector?: Injector }, -): Signal -export function injectStore>( - store: Atom | ReadonlyAtom, - selector?: (state: NoInfer) => TSelected, - options?: CreateSignalOptions & { injector?: Injector }, -): Signal -export function injectStore< - TState extends StoreContext, - TSelected = NoInfer, ->( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => - d as unknown as TSelected, - options: CreateSignalOptions & { injector?: Injector } = { - equal: shallow, - }, -): Signal { - !options.injector && assertInInjectionContext(injectStore) +export interface InjectSelectorOptions extends Omit< + CreateSignalOptions, + 'equal' +> { + compare?: (a: TSelected, b: TSelected) => boolean + injector?: Injector +} + +type CompatibilityInjectStoreOptions = + CreateSignalOptions & { + injector?: Injector + } - if (!options.injector) { - options.injector = inject(Injector) +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +function resolveInjector( + fn: (...args: Array) => unknown, + injector?: Injector, +) { + if (!injector) { + assertInInjectionContext(fn) + return inject(Injector) + } + + return injector +} - return runInInjectionContext(options.injector, () => { +function createReadonlySelectionSignal( + source: SelectionSource, + selector: (state: NoInfer) => TSelected, + options?: InjectSelectorOptions, +): Signal { + const injector = resolveInjector( + createReadonlySelectionSignal, + options?.injector, + ) + + return runInInjectionContext(injector, () => { const destroyRef = inject(DestroyRef) - const slice = linkedSignal(() => selector(store.get()), options) + const compare = options?.compare ?? defaultCompare + const { + injector: _injector, + compare: _compare, + ...signalOptions + } = options ?? {} + const slice = linkedSignal(() => selector(source.get()), { + ...signalOptions, + equal: compare, + }) - const { unsubscribe } = store.subscribe((s) => { - slice.set(selector(s)) + const { unsubscribe } = source.subscribe((state) => { + slice.set(selector(state)) }) destroyRef.onDestroy(() => { @@ -56,7 +88,174 @@ export function injectStore< }) } -function shallow(objA: T, objB: T) { +/** + * Selects a slice of state from an atom or store and returns it as an Angular + * signal. + * + * This is the primary Angular read hook for TanStack Store. + * + * @example + * ```ts + * readonly count = injectSelector(counterStore, (state) => state.count) + * ``` + * + * @example + * ```ts + * readonly doubled = injectSelector(countAtom, (value) => value * 2) + * ``` + */ +export function injectSelector>( + source: SelectionSource, + selector: (state: NoInfer) => TSelected = (d) => + d as unknown as TSelected, + options?: InjectSelectorOptions, +): Signal { + return createReadonlySelectionSignal(source, selector, options) +} + +/** + * Returns the current value signal for an atom or store. + * + * This is the whole-value counterpart to {@link injectSelector}. + * + * @example + * ```ts + * readonly count = injectValue(countAtom) + * ``` + * + * @example + * ```ts + * readonly state = injectValue(counterStore) + * ``` + */ +export function injectValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: InjectSelectorOptions, +): Signal { + return injectSelector(source, (value) => value, options) +} + +/** + * Returns a stable setter for a writable atom or store. + * + * Writable atoms preserve their native `set` contract. Writable stores + * preserve their native `setState` contract. + * + * @example + * ```ts + * readonly setCount = injectSetValue(countAtom) + * + * increment() { + * this.setCount((prev) => prev + 1) + * } + * ``` + * + * @example + * ```ts + * readonly setState = injectSetValue(counterStore) + * ``` + */ +export function injectSetValue( + source: Atom, +): Atom['set'] +export function injectSetValue( + source: Store, +): Store['setState'] +export function injectSetValue( + source: Atom | Store, +) { + return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { + if ('setState' in source) { + source.setState(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + if (typeof valueOrUpdater === 'function') { + source.set(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + source.set(valueOrUpdater) + } + } + }) as Atom['set'] | Store['setState'] +} + +/** + * Returns the current atom signal together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * readonly atomTuple = injectAtom(countAtom) + * readonly count = this.atomTuple[0] + * readonly setCount = this.atomTuple[1] + * ``` + */ +export function injectAtom( + atom: Atom, + options?: InjectSelectorOptions, +): [Signal, Atom['set']] { + const value = injectValue(atom, options) + const setValue = injectSetValue(atom) + + return [value, setValue] +} + +/** + * Returns the stable actions bag from a writable store created with actions. + * + * Use this when a component only needs to call store actions and should not + * subscribe to store state. + * + * @example + * ```ts + * readonly actions = injectStoreActions(counterStore) + * + * increment() { + * this.actions.increment() + * } + * ``` + */ +export function injectStoreActions( + store: Store, +): TActions { + return store.actions +} + +/** + * Deprecated alias for {@link injectSelector}. + * + * @example + * ```ts + * readonly count = injectStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `injectSelector` instead. + */ +export function injectStore>( + store: SelectionSource, + selector?: (state: NoInfer) => TSelected, + options?: CompatibilityInjectStoreOptions, +): Signal +export function injectStore>( + store: SelectionSource, + selector: (state: NoInfer) => TSelected = (d) => + d as unknown as TSelected, + options?: CompatibilityInjectStoreOptions, +): Signal { + const { equal, injector, ...signalOptions } = options ?? {} + + return injectSelector(store, selector, { + ...signalOptions, + compare: equal, + injector, + }) +} + +export function shallow(objA: T, objB: T) { if (Object.is(objA, objB)) { return true } @@ -91,18 +290,25 @@ function shallow(objA: T, objB: T) { return true } - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { + const keysA = getOwnKeys(objA) + if (keysA.length !== getOwnKeys(objB).length) { return false } - for (const key of keysA) { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < keysA.length; i++) { if ( - !Object.prototype.hasOwnProperty.call(objB, key) || - !Object.is(objA[key as keyof T], objB[key as keyof T]) + !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || + !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) ) { return false } } return true } + +function getOwnKeys(obj: object): Array { + return (Object.keys(obj) as Array).concat( + Object.getOwnPropertySymbols(obj), + ) +} diff --git a/packages/angular-store/tests/index.test.ts b/packages/angular-store/tests/index.test.ts index 88da8df7..5645f975 100644 --- a/packages/angular-store/tests/index.test.ts +++ b/packages/angular-store/tests/index.test.ts @@ -2,11 +2,91 @@ import { describe, expect, test } from 'vitest' import { Component, effect } from '@angular/core' import { TestBed } from '@angular/core/testing' import { By } from '@angular/platform-browser' -import { createStore } from '@tanstack/store' -import { injectStore } from '../src/index' +import { createAtom, createStore } from '@tanstack/store' +import { + injectAtom, + injectSelector, + injectSetValue, + injectStore, + injectStoreActions, + injectValue, +} from '../src/index' -describe('injectStore', () => { - test(`allows us to select state using a selector`, () => { +describe('atom hooks', () => { + test('injectValue reads mutable atom state and rerenders when updated', () => { + const atom = createAtom(0) + + @Component({ + template: ` +
+

Value: {{ value() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + value = injectValue(atom) + + update() { + atom.set((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 0') + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 1') + }) + + test('injectAtom returns the current signal and setter', () => { + const atom = createAtom(0) + + @Component({ + template: ` +
+

Value: {{ value() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + value!: ReturnType>[0] + setValue!: ReturnType>[1] + + constructor() { + ;[this.value, this.setValue] = injectAtom(atom) + } + + add() { + this.setValue((prev) => prev + 5) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 0') + + fixture.debugElement + .query(By.css('button#add')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 5') + }) +}) + +describe('selector hooks', () => { + test('allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1 }) @Component({ @@ -14,14 +94,61 @@ describe('injectStore', () => { standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.select) + storeVal = injectSelector(store, (state) => state.select) } const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - const element = fixture.nativeElement - expect(element.textContent).toContain('Store: 0') + expect(fixture.nativeElement.textContent).toContain('Store: 0') + }) + + test('injectValue reads writable and readonly store state', () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + @Component({ + template: ` +
+

{{ value() }}

+

{{ readonlyValue().value }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + value = injectValue(baseStore) + readonlyValue = injectValue(readonlyStore) + + update() { + baseStore.setState((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('1') + expect( + fixture.debugElement.query(By.css('p#readonly')).nativeElement + .textContent, + ).toContain('2') + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('2') + expect( + fixture.debugElement.query(By.css('p#readonly')).nativeElement + .textContent, + ).toContain('4') }) test('only triggers a re-render when selector state is updated', () => { @@ -43,11 +170,11 @@ describe('injectStore', () => { standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.select) + storeVal = injectSelector(store, (state) => state.select) constructor() { effect(() => { - console.log(this.storeVal()) + this.storeVal() count++ }) } @@ -70,27 +197,209 @@ describe('injectStore', () => { const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - const element = fixture.nativeElement - const debugElement = fixture.debugElement - - expect(element.textContent).toContain('Store: 0') + expect(fixture.nativeElement.textContent).toContain('Store: 0') expect(count).toEqual(1) - debugElement + fixture.debugElement .query(By.css('button#updateSelect')) .triggerEventHandler('click', null) + fixture.detectChanges() + expect(fixture.nativeElement.textContent).toContain('Store: 10') + expect(count).toEqual(2) + fixture.debugElement + .query(By.css('button#updateIgnored')) + .triggerEventHandler('click', null) fixture.detectChanges() - expect(element.textContent).toContain('Store: 10') + expect(fixture.nativeElement.textContent).toContain('Store: 10') expect(count).toEqual(2) + }) + + test('injectSelector allows specifying a custom equality function', () => { + const store = createStore({ + array: [ + { select: 0, ignore: 1 }, + { select: 0, ignore: 1 }, + ], + }) + let count = 0 + + @Component({ + template: ` +
+

{{ sum() }}

+ + +
+ `, + standalone: true, + }) + class MyCmp { + sum = injectSelector( + store, + (state) => + state.array + .map(({ ignore, ...rest }) => rest) + .reduce((total, item) => total + item.select, 0), + { + compare: (prev, next) => prev === next, + }, + ) - debugElement + constructor() { + effect(() => { + this.sum() + count++ + }) + } + + updateSelect() { + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + select: item.select + 5, + })), + })) + } + + updateIgnored() { + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + ignore: item.ignore + 1, + })), + })) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('0') + expect(count).toBe(1) + + fixture.debugElement .query(By.css('button#updateIgnored')) .triggerEventHandler('click', null) + fixture.detectChanges() + expect(count).toBe(1) + fixture.debugElement + .query(By.css('button#updateSelect')) + .triggerEventHandler('click', null) fixture.detectChanges() - expect(element.textContent).toContain('Store: 10') - expect(count).toEqual(2) + expect(fixture.nativeElement.textContent).toContain('10') + expect(count).toBe(2) + }) + + test('injectSelector works with mounted derived stores', () => { + const store = createStore(0) + const derived = createStore(() => ({ val: store.state * 2 })) + + @Component({ + template: ` +
+

{{ derivedVal() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + derivedVal = injectSelector(derived, (state) => state.val) + + update() { + store.setState((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#derived')).nativeElement.textContent, + ).toContain('0') + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#derived')).nativeElement.textContent, + ).toContain('2') + }) + + test('injectSetValue updates stores by updater', () => { + const store = createStore(0) + + @Component({ + template: ``, + standalone: true, + }) + class MyCmp { + updateState = injectSetValue(store) + + update() { + this.updateState((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + expect(store.state).toBe(1) + }) + + test('injectStoreActions returns the stable actions bag', () => { + const store = createStore({ count: 0 }, ({ set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + })) + + @Component({ + template: ``, + standalone: true, + }) + class MyCmp { + actions = injectStoreActions(store) + + update() { + this.actions.inc() + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + expect(store.state.count).toBe(1) + }) +}) + +describe('injectStore', () => { + test('is a compatibility alias for injectSelector', () => { + const store = createStore({ select: 0 }) + + @Component({ + template: `

Store: {{ storeVal() }}

`, + standalone: true, + }) + class MyCmp { + storeVal = injectStore(store, (state) => state.select) + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Store: 0') }) }) @@ -108,13 +417,7 @@ describe('dataType', () => { standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.date) - - constructor() { - effect(() => { - console.log(this.storeVal()) - }) - } + storeVal = injectSelector(store, (state) => state.date) updateDate() { store.setState((v) => ({ @@ -127,19 +430,18 @@ describe('dataType', () => { const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - const debugElement = fixture.debugElement - expect( - debugElement.query(By.css('p#displayStoreVal')).nativeElement.textContent, + fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement + .textContent, ).toContain(new Date('2025-03-29T21:06:30.401Z')) - debugElement + fixture.debugElement .query(By.css('button#updateDate')) .triggerEventHandler('click', null) - fixture.detectChanges() expect( - debugElement.query(By.css('p#displayStoreVal')).nativeElement.textContent, + fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement + .textContent, ).toContain(new Date('2025-03-29T21:06:40.401Z')) }) }) diff --git a/packages/angular-store/tests/test.test-d.ts b/packages/angular-store/tests/test.test-d.ts index 430a75a7..c04b1668 100644 --- a/packages/angular-store/tests/test.test-d.ts +++ b/packages/angular-store/tests/test.test-d.ts @@ -1,16 +1,94 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { injectStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + injectAtom, + injectSelector, + injectSetValue, + injectStore, + injectStoreActions, + injectValue, +} from '../src' import type { Signal } from '@angular/core' +import type { Atom } from '@tanstack/store' -test('injectStore works with derived state', () => { +test('injectSelector works with derived state', () => { const store = createStore(12) const derived = createStore(() => store.state * 2) - const val = injectStore(derived, (state) => { + const val = injectSelector(derived, (state) => { expectTypeOf(state).toEqualTypeOf() return state }) expectTypeOf(val).toEqualTypeOf>() }) + +test('injectValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(injectValue(writableAtom)).toEqualTypeOf>() + expectTypeOf(injectValue(readonlyAtom)).toEqualTypeOf>() + expectTypeOf(injectValue(writableStore)).toEqualTypeOf>() + expectTypeOf(injectValue(readonlyStore)).toEqualTypeOf>() +}) + +test('injectSetValue preserves native setter contracts', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(injectSetValue(writableAtom)).toEqualTypeOf< + Atom['set'] + >() + expectTypeOf(injectSetValue(writableStore)).toEqualTypeOf< + typeof writableStore.setState + >() + // @ts-expect-error readonly atoms cannot be set + injectSetValue(readonlyAtom) + // @ts-expect-error readonly stores cannot be set + injectSetValue(readonlyStore) +}) + +test('injectAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = injectAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with injectAtom + injectAtom(readonlyAtom) +}) + +test('injectStore matches injectSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = injectSelector(store, (state) => state) + const compatValue = injectStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf>() + expectTypeOf(compatValue).toEqualTypeOf>() +}) + +test('injectStoreActions infers the action bag from writable stores', () => { + const store = createStore({ count: 0 }, ({ get, set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })) + + const actions = injectStoreActions(store) + + expectTypeOf(actions.inc).toBeFunction() + expectTypeOf(actions.current()).toExtend() + + const plainStore = createStore(12) + expectTypeOf(injectStoreActions(plainStore)).toEqualTypeOf() + + const readonlyStore = createStore(() => 24) + // @ts-expect-error readonly stores do not expose actions + injectStoreActions(readonlyStore) +}) diff --git a/packages/preact-store/src/createStoreContext.tsx b/packages/preact-store/src/createStoreContext.tsx new file mode 100644 index 00000000..097cfcc7 --- /dev/null +++ b/packages/preact-store/src/createStoreContext.tsx @@ -0,0 +1,72 @@ +// eslint-disable-next-line import/consistent-type-specifier-style +import { type ComponentChildren, createContext } from 'preact' +import { useContext } from 'preact/hooks' + +/** + * Creates a typed Preact context for sharing a bundle of atoms and stores with + * a subtree. + * + * The returned `StoreProvider` only transports the provided object through + * Preact context. Consumers destructure the contextual atoms and stores, then + * compose them with the existing hooks like {@link useSelector}, + * {@link useValue}, {@link useSetValue}, and {@link useAtom}. + * + * The object shape is preserved exactly, so keyed atoms and stores remain fully + * typed when read back with `useStoreContext()`. + * + * @example + * ```tsx + * const { StoreProvider, useStoreContext } = createStoreContext<{ + * countAtom: Atom + * totalsStore: Store<{ count: number }> + * }>() + * + * function CountButton() { + * const { countAtom, totalsStore } = useStoreContext() + * const count = useValue(countAtom) + * const total = useSelector(totalsStore, (state) => state.count) + * + * return ( + * + * ) + * } + * ``` + * + * @throws When `useStoreContext()` is called outside the matching `StoreProvider`. + */ +export function createStoreContext() { + const Context = createContext(null) + Context.displayName = 'StoreContext' + + function StoreProvider({ + children, + value, + }: { + children?: ComponentChildren + value: TValue + }) { + return {children} + } + + function useStoreContext() { + const value = useContext(Context) + + if (value === null) { + throw new Error('Missing StoreProvider for StoreContext') + } + + return value + } + + return { + StoreProvider, + useStoreContext, + } +} diff --git a/packages/preact-store/src/index.ts b/packages/preact-store/src/index.ts index c4572691..0716abab 100644 --- a/packages/preact-store/src/index.ts +++ b/packages/preact-store/src/index.ts @@ -1,171 +1,17 @@ -import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks' -import type { Atom, ReadonlyAtom } from '@tanstack/store' - export * from '@tanstack/store' -type InternalStore = { - _value: any - _getSnapshot: () => any -} - -type StoreRef = { - _instance: InternalStore -} - -/** - * This is taken from https://github.com/preactjs/preact/blob/main/compat/src/hooks.js#L8-L54 - * which is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 - * on a high level this cuts out the warnings, ... and attempts a smaller implementation. - * This way we don't have to import preact/compat with side effects - */ -function useSyncExternalStore( - subscribe: (onStoreChange: () => void) => () => void, - getSnapshot: () => any, -) { - const value = getSnapshot() - - const [{ _instance }, forceUpdate] = useState({ - _instance: { _value: value, _getSnapshot: getSnapshot }, - }) - - useLayoutEffect(() => { - _instance._value = value - _instance._getSnapshot = getSnapshot - - if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }) - } - }, [subscribe, value, getSnapshot]) - - useEffect(() => { - if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }) - } - - return subscribe(() => { - if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }) - } - }) - }, [subscribe]) - - return value -} - -function didSnapshotChange(inst: { - _getSnapshot: () => any - _value: any -}): boolean { - const latestGetSnapshot = inst._getSnapshot - const prevValue = inst._value - try { - const nextValue = latestGetSnapshot() - return !Object.is(prevValue, nextValue) - // eslint-disable-next-line no-unused-vars - } catch (_error) { - return true - } -} - -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -function useSyncExternalStoreWithSelector( - subscribe: (onStoreChange: () => void) => () => void, - getSnapshot: () => TSnapshot, - selector: (snapshot: TSnapshot) => TSelected, - isEqual: (a: TSelected, b: TSelected) => boolean, -): TSelected { - const selectedSnapshotRef = useRef() - - const getSelectedSnapshot = () => { - const snapshot = getSnapshot() - const selected = selector(snapshot) - - if ( - selectedSnapshotRef.current === undefined || - !isEqual(selectedSnapshotRef.current, selected) - ) { - selectedSnapshotRef.current = selected - } - - return selectedSnapshotRef.current - } - - return useSyncExternalStore(subscribe, getSelectedSnapshot) -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): TSelected { - const equal = options.equal ?? shallow - const slice = useSyncExternalStoreWithSelector( - (onStoreChange) => store.subscribe(onStoreChange).unsubscribe, - () => store.get(), - selector, - equal, - ) - - return slice -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } +export * from './createStoreContext' +export * from './useCreateAtom' +export * from './useCreateStore' - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } +export * from './useSetValue' +export * from './useStoreActions' - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } +export * from './useValue' +export * from './useSelector' - for (const key of keysA) { - if ( - !Object.prototype.hasOwnProperty.call(objB, key as string) || - !Object.is(objA[key as keyof T], objB[key as keyof T]) - ) { - return false - } - } - return true -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} +// comparators +export * from './shallow' diff --git a/packages/preact-store/src/shallow.ts b/packages/preact-store/src/shallow.ts new file mode 100644 index 00000000..75ff1634 --- /dev/null +++ b/packages/preact-store/src/shallow.ts @@ -0,0 +1,57 @@ +export function shallow(objA: T, objB: T) { + if (Object.is(objA, objB)) { + return true + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + if (objA instanceof Map && objB instanceof Map) { + if (objA.size !== objB.size) return false + for (const [k, v] of objA) { + if (!objB.has(k) || !Object.is(v, objB.get(k))) return false + } + return true + } + + if (objA instanceof Set && objB instanceof Set) { + if (objA.size !== objB.size) return false + for (const v of objA) { + if (!objB.has(v)) return false + } + return true + } + + if (objA instanceof Date && objB instanceof Date) { + if (objA.getTime() !== objB.getTime()) return false + return true + } + + const keysA = getOwnKeys(objA) + if (keysA.length !== getOwnKeys(objB).length) { + return false + } + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || + !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) + ) { + return false + } + } + return true +} + +function getOwnKeys(obj: object): Array { + return (Object.keys(obj) as Array).concat( + Object.getOwnPropertySymbols(obj), + ) +} diff --git a/packages/preact-store/src/useAtom.ts b/packages/preact-store/src/useAtom.ts new file mode 100644 index 00000000..eb2b47f7 --- /dev/null +++ b/packages/preact-store/src/useAtom.ts @@ -0,0 +1,31 @@ +import { useSetValue } from './useSetValue' +import { useValue } from './useValue' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom value together with a stable setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```tsx + * const [count, setCount] = useAtom(countAtom) + * + * return ( + * + * ) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [TValue, Atom['set']] { + const value = useValue(atom, options) + const setValue = useSetValue(atom) + + return [value, setValue] +} diff --git a/packages/preact-store/src/useCreateAtom.ts b/packages/preact-store/src/useCreateAtom.ts new file mode 100644 index 00000000..08b3e2be --- /dev/null +++ b/packages/preact-store/src/useCreateAtom.ts @@ -0,0 +1,48 @@ +import { useState } from 'preact/hooks' +import { createAtom } from '@tanstack/store' +// eslint-disable-next-line no-duplicate-imports +import type { Atom, AtomOptions, ReadonlyAtom } from '@tanstack/store' + +/** + * Creates a stable atom instance for the lifetime of the component. + * + * Pass an initial value to create a writable atom, or a getter function to + * create a readonly derived atom. This mirrors {@link createAtom}, but only + * creates the atom once per component mount. + * + * @example + * ```tsx + * function Counter() { + * const countAtom = useCreateAtom(0) + * const [count, setCount] = useAtom(countAtom) + * + * return ( + * + * ) + * } + * ``` + */ +export function useCreateAtom( + getValue: (prev?: NoInfer) => T, + options?: AtomOptions, +): ReadonlyAtom +export function useCreateAtom( + initialValue: T, + options?: AtomOptions, +): Atom +export function useCreateAtom( + valueOrFn: T | ((prev?: T) => T), + options?: AtomOptions, +): Atom | ReadonlyAtom { + const [atom] = useState | ReadonlyAtom>(() => { + if (typeof valueOrFn === 'function') { + return createAtom(valueOrFn as (prev?: NoInfer) => T, options) + } + + return createAtom(valueOrFn, options) + }) + + return atom +} diff --git a/packages/preact-store/src/useCreateStore.ts b/packages/preact-store/src/useCreateStore.ts new file mode 100644 index 00000000..1f518238 --- /dev/null +++ b/packages/preact-store/src/useCreateStore.ts @@ -0,0 +1,65 @@ +import { useState } from 'preact/hooks' +import { createStore } from '@tanstack/store' +// eslint-disable-next-line no-duplicate-imports +import type { + ReadonlyStore, + Store, + StoreActionMap, + StoreActionsFactory, +} from '@tanstack/store' + +type NonFunction = T extends (...args: Array) => any ? never : T + +/** + * Creates a stable store instance for the lifetime of the component. + * + * Pass an initial value to create a writable store, or a getter function to + * create a readonly derived store. This mirrors {@link createStore}, but only + * creates the store once per component mount. + * + * @example + * ```tsx + * function Counter() { + * const counterStore = useCreateStore({ count: 0 }) + * const count = useSelector(counterStore, (state) => state.count) + * const setState = useSetValue(counterStore) + * + * return ( + * + * ) + * } + * ``` + */ +export function useCreateStore( + getValue: (prev?: NoInfer) => T, +): ReadonlyStore +export function useCreateStore(initialValue: T): Store +export function useCreateStore( + initialValue: NonFunction, + actions: StoreActionsFactory, +): Store +export function useCreateStore( + valueOrFn: T | ((prev?: T) => T), + actions?: StoreActionsFactory, +): Store | Store | ReadonlyStore { + const [store] = useState | Store | ReadonlyStore>( + () => { + if (typeof valueOrFn === 'function') { + return createStore(valueOrFn as (prev?: NoInfer) => T) + } + + if (actions) { + return createStore(valueOrFn as NonFunction, actions) + } + + return createStore(valueOrFn) + }, + ) + + return store +} diff --git a/packages/preact-store/src/useSelector.ts b/packages/preact-store/src/useSelector.ts new file mode 100644 index 00000000..fc833e25 --- /dev/null +++ b/packages/preact-store/src/useSelector.ts @@ -0,0 +1,150 @@ +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, +} from 'preact/hooks' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type InternalStore = { + _value: any + _getSnapshot: () => any +} + +type StoreRef = { + _instance: InternalStore +} + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * This is taken from https://github.com/preactjs/preact/blob/main/compat/src/hooks.js#L8-L54 + * which is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 + * on a high level this cuts out the warnings, ... and attempts a smaller implementation. + * This way we don't have to import preact/compat with side effects + */ +function useSyncExternalStore( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => any, +) { + const value = getSnapshot() + + const [{ _instance }, forceUpdate] = useState({ + _instance: { _value: value, _getSnapshot: getSnapshot }, + }) + + useLayoutEffect(() => { + _instance._value = value + _instance._getSnapshot = getSnapshot + + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }) + } + }, [_instance, subscribe, value, getSnapshot]) + + useEffect(() => { + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }) + } + + return subscribe(() => { + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }) + } + }) + }, [_instance, subscribe]) + + return value +} + +function didSnapshotChange(inst: InternalStore): boolean { + const latestGetSnapshot = inst._getSnapshot + const prevValue = inst._value + try { + const nextValue = latestGetSnapshot() + return !Object.is(prevValue, nextValue) + // eslint-disable-next-line no-unused-vars + } catch (_error) { + return true + } +} + +function useSyncExternalStoreWithSelector( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => TSnapshot, + selector: (snapshot: TSnapshot) => TSelected, + compare: (a: TSelected, b: TSelected) => boolean, +): TSelected { + const selectedSnapshotRef = useRef() + + const getSelectedSnapshot = () => { + const snapshot = getSnapshot() + const selected = selector(snapshot) + + if ( + selectedSnapshotRef.current === undefined || + !compare(selectedSnapshotRef.current, selected) + ) { + selectedSnapshotRef.current = selected + } + + return selectedSnapshotRef.current + } + + return useSyncExternalStore(subscribe, getSelectedSnapshot) +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary Preact read hook for TanStack Store. Use it when a + * component only needs part of a source value. + * + * @example + * ```tsx + * const count = useSelector(counterStore, (state) => state.count) + * ``` + * + * @example + * ```tsx + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): TSelected { + const compare = options?.compare ?? defaultCompare + + const subscribe = useCallback( + (handleStoreChange: () => void) => { + const { unsubscribe } = source.subscribe(handleStoreChange) + return unsubscribe + }, + [source], + ) + + const getSnapshot = useCallback(() => source.get(), [source]) + + return useSyncExternalStoreWithSelector( + subscribe, + getSnapshot, + selector, + compare, + ) +} diff --git a/packages/preact-store/src/useSetValue.ts b/packages/preact-store/src/useSetValue.ts new file mode 100644 index 00000000..4eeeb0a2 --- /dev/null +++ b/packages/preact-store/src/useSetValue.ts @@ -0,0 +1,41 @@ +import { useCallback } from 'preact/hooks' +import type { Atom, Store, StoreActionMap } from '@tanstack/store' + +/** + * Returns a stable setter for a writable atom or store. + * + * Writable atoms preserve their native `set` contract. Writable stores + * preserve their native `setState` contract. + * + * @example + * ```tsx + * const setCount = useSetValue(countAtom) + * setCount((prev) => prev + 1) + * ``` + * + * @example + * ```tsx + * const setState = useSetValue(counterStore) + * setState((state) => ({ ...state, count: state.count + 1 })) + * ``` + */ +export function useSetValue(source: Atom): Atom['set'] +export function useSetValue( + source: Store, +): Store['setState'] +export function useSetValue( + source: Atom | Store, +) { + return useCallback['set'] | Store['setState']>( + (valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { + if ('setState' in source) { + source.setState(valueOrUpdater as (prevVal: TValue) => TValue) + } else if (typeof valueOrUpdater === 'function') { + source.set(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + source.set(valueOrUpdater) + } + }, + [source], + ) +} diff --git a/packages/preact-store/src/useStore.ts b/packages/preact-store/src/useStore.ts new file mode 100644 index 00000000..b08021a1 --- /dev/null +++ b/packages/preact-store/src/useStore.ts @@ -0,0 +1,22 @@ +import { useSelector } from './useSelector' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```tsx + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +) => useSelector(source, selector, { compare }) diff --git a/packages/preact-store/src/useStoreActions.ts b/packages/preact-store/src/useStoreActions.ts new file mode 100644 index 00000000..0e4996c3 --- /dev/null +++ b/packages/preact-store/src/useStoreActions.ts @@ -0,0 +1,20 @@ +import { useMemo } from 'preact/hooks' +import type { Store, StoreActionMap } from '@tanstack/store' + +/** + * Returns the stable actions bag from a writable store created with actions. + * + * Use this when a component only needs to call store actions and should not + * subscribe to store state. + * + * @example + * ```tsx + * const actions = useStoreActions(counterStore) + * actions.increment() + * ``` + */ +export function useStoreActions( + store: Store, +): TActions { + return useMemo(() => store.actions, [store]) +} diff --git a/packages/preact-store/src/useValue.ts b/packages/preact-store/src/useValue.ts new file mode 100644 index 00000000..a0e037e8 --- /dev/null +++ b/packages/preact-store/src/useValue.ts @@ -0,0 +1,31 @@ +import { useSelector } from './useSelector' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +// eslint-disable-next-line no-duplicate-imports +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when the + * component needs the entire current value from a source. + * + * @example + * ```tsx + * const count = useValue(countAtom) + * ``` + * + * @example + * ```tsx + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): TValue { + return useSelector(source, (value) => value, options) +} diff --git a/packages/preact-store/tests/index.test.tsx b/packages/preact-store/tests/index.test.tsx index 8012ddf9..4b01693b 100644 --- a/packages/preact-store/tests/index.test.tsx +++ b/packages/preact-store/tests/index.test.tsx @@ -1,20 +1,426 @@ -import { describe, expect, it, test, vi } from 'vitest' -import { render, waitFor } from '@testing-library/preact' +import { act, render, renderHook, waitFor } from '@testing-library/preact' import { userEvent } from '@testing-library/user-event' -import { createStore } from '@tanstack/store' -import { shallow, useStore } from '../src/index' +import { describe, expect, it, test, vi } from 'vitest' +import { createAtom, createStore } from '@tanstack/store' +import { + createStoreContext, + shallow, + useAtom, + useCreateAtom, + useCreateStore, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src/index' const user = userEvent.setup() -describe('useStore', () => { - it('allows us to select state using a selector', () => { +describe('atom hooks', () => { + it('useCreateAtom creates a stable atom instance across rerenders', () => { + const { result, rerender } = renderHook(() => useCreateAtom(0)) + const atom = result.current + + act(() => { + atom.set(1) + }) + + rerender() + + expect(result.current).toBe(atom) + expect(result.current.get()).toBe(1) + }) + + it('useValue reads mutable atom state and rerenders when updated', async () => { + const atom = createAtom(0) + + function Comp() { + const value = useValue(atom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('useValue reads readonly atom state', async () => { + const countAtom = createAtom(1) + const doubledAtom = createAtom(() => countAtom.get() * 2) + + function Comp() { + const value = useValue(doubledAtom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 4')).toBeInTheDocument()) + }) + + it('useValue respects custom compare', async () => { + const atom = createAtom({ + select: 0, + ignored: 1, + }) + const renderSpy = vi.fn() + + function Comp() { + const value = useValue(atom, { + compare: (prev, next) => prev.select === next.select, + }) + renderSpy() + + return ( +
+

Renders: {renderSpy.mock.calls.length}

+

Value: {value.select}

+ + +
+ ) + } + + const { getByText } = render() + + expect(getByText('Renders: 1')).toBeInTheDocument() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update ignored')) + expect(getByText('Renders: 1')).toBeInTheDocument() + + await user.click(getByText('Update select')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + expect(getByText('Renders: 2')).toBeInTheDocument() + }) + + it('useSetValue updates atoms by value and updater and stays stable', () => { + const atom = createAtom(0) + const { result, rerender } = renderHook(() => useSetValue(atom)) + const setAtom = result.current + + act(() => { + result.current(1) + }) + expect(atom.get()).toBe(1) + + rerender() + expect(result.current).toBe(setAtom) + + act(() => { + result.current((prev) => prev + 1) + }) + expect(atom.get()).toBe(2) + }) + + it('useAtom returns the current value and setter', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useAtom(atom)) + + expect(result.current[0]).toBe(0) + + act(() => { + result.current[1]((prev) => prev + 5) + }) + + expect(result.current[0]).toBe(5) + }) + + it('useSetValue updates stores by updater and stays stable', () => { + const store = createStore(0) + const { result, rerender } = renderHook(() => useSetValue(store)) + const setStore = result.current + + act(() => { + result.current((prev) => prev + 1) + }) + expect(store.state).toBe(1) + + rerender() + expect(result.current).toBe(setStore) + + act(() => { + result.current((prev) => prev + 1) + }) + expect(store.state).toBe(2) + }) +}) + +describe('store contexts', () => { + it('provides bundled writable atoms and stores', async () => { + const countAtom = createAtom(0) + const totalStore = createStore({ count: 0 }) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof countAtom + totalStore: typeof totalStore + }>() + + function Comp() { + const { countAtom: currentAtom, totalStore: currentStore } = + useStoreContext() + const value = useValue(currentAtom) + const setValue = useSetValue(currentAtom) + const total = useSelector(currentStore, (state) => state.count) + const setTotal = useSetValue(currentStore) + + return ( +
+

Value: {value}

+

Total: {total}

+ + +
+ ) + } + + const { getByText } = render( + + + , + ) + + expect(getByText('Value: 0')).toBeInTheDocument() + expect(getByText('Total: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + await user.click(getByText('Update total')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Total: 1')).toBeInTheDocument()) + }) + + it('supports readonly atoms and stores in the same context', async () => { + const baseAtom = createAtom(1) + const readonlyAtom = createAtom(() => baseAtom.get() * 2) + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + const { StoreProvider, useStoreContext } = createStoreContext<{ + readonlyAtom: typeof readonlyAtom + readonlyStore: typeof readonlyStore + }>() + + function Comp() { + const { readonlyAtom: currentAtom, readonlyStore: currentStore } = + useStoreContext() + const atomValue = useValue(currentAtom) + const storeValue = useSelector(currentStore, (state) => state.value) + + return ( +
+

Atom: {atomValue}

+

Store: {storeValue}

+
+ ) + } + + const { getByText } = render( + + + , + ) + + expect(getByText('Atom: 2')).toBeInTheDocument() + expect(getByText('Store: 2')).toBeInTheDocument() + + act(() => { + baseAtom.set((prev) => prev + 1) + baseStore.setState((prev) => prev + 1) + }) + + await waitFor(() => expect(getByText('Atom: 4')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Store: 4')).toBeInTheDocument()) + }) + + it('works with useAtom against contextual atoms', async () => { + const countAtom = createAtom(0) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof countAtom + }>() + + function Comp() { + const { countAtom: atom } = useStoreContext() + const [value, setValue] = useAtom(atom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render( + + + , + ) + + await user.click(getByText('Add 5')) + + await waitFor(() => expect(getByText('Value: 5')).toBeInTheDocument()) + }) + + it('throws a clear error when a store provider is missing', () => { + const { useStoreContext } = createStoreContext<{ countAtom: number }>() + + function Comp() { + useStoreContext() + return null + } + + expect(() => render()).toThrowError( + 'Missing StoreProvider for StoreContext', + ) + }) + + it('nested providers override parent values', async () => { + const outerAtom = createAtom(1) + const innerAtom = createAtom(5) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof outerAtom + }>() + + function Value() { + const { countAtom: atom } = useStoreContext() + const value = useValue(atom) + + return

Value: {value}

+ } + + const { getAllByText } = render( + + + + + + , + ) + + expect(getAllByText(/Value:/).map((node) => node.textContent)).toEqual([ + 'Value: 1', + 'Value: 5', + ]) + + act(() => { + innerAtom.set(7) + }) + + await waitFor(() => + expect(getAllByText(/Value:/).map((node) => node.textContent)).toEqual([ + 'Value: 1', + 'Value: 7', + ]), + ) + }) +}) + +describe('store hooks', () => { + it('useCreateStore creates a stable store instance across rerenders', () => { + const { result, rerender } = renderHook(() => useCreateStore(0)) + const store = result.current + + act(() => { + store.setState((prev) => prev + 1) + }) + + rerender() + + expect(result.current).toBe(store) + expect(result.current.state).toBe(1) + }) + + it('useCreateStore supports actions and keeps them stable', () => { + const { result, rerender } = renderHook(() => + useCreateStore({ count: 0 }, ({ get, set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })), + ) + const store = result.current + const actions = store.actions + + act(() => { + store.actions.inc() + }) + + rerender() + + expect(result.current).toBe(store) + expect(result.current.actions).toBe(actions) + expect(result.current.actions.current()).toBe(1) + }) + + it('useSelector allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1, }) function Comp() { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) return

Store: {storeVal}

} @@ -23,8 +429,40 @@ describe('useStore', () => { expect(getByText('Store: 0')).toBeInTheDocument() }) - // This should ideally test the custom uSES hook - it('only triggers a re-render when selector state is updated', async () => { + it('useValue reads writable and readonly store state', async () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + function Comp() { + const value = useValue(baseStore) + const readonlyValue = useValue(readonlyStore) + + return ( +
+

Value: {value}

+

Readonly: {readonlyValue.value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) + + it('useSelector only triggers a re-render when selector state is updated', async () => { const store = createStore({ select: 0, ignored: 1, @@ -32,7 +470,7 @@ describe('useStore', () => { const renderSpy = vi.fn() function Comp() { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) renderSpy() return ( @@ -78,7 +516,7 @@ describe('useStore', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) - it('allow specifying custom equality function', async () => { + it('useSelector allows specifying a custom equality function', async () => { const store = createStore({ array: [ { select: 0, ignore: 1 }, @@ -92,10 +530,10 @@ describe('useStore', () => { const renderSpy = vi.fn() function Comp() { - const storeVal = useStore( + const storeVal = useSelector( store, (state) => state.array.map(({ ignore, ...rest }) => rest), - { equal: deepEqual }, + { compare: deepEqual }, ) renderSpy() @@ -150,13 +588,12 @@ describe('useStore', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) - it('works with mounted derived stores', async () => { + it('useSelector works with mounted derived stores', async () => { const store = createStore(0) - - const derived = createStore(() => store.state * 2) + const derived = createStore(() => ({ val: store.state * 2 })) function Comp() { - const derivedVal = useStore(derived, (state) => state) + const derivedVal = useSelector(derived, (state) => state.val) return (
@@ -175,6 +612,79 @@ describe('useStore', () => { await waitFor(() => expect(getByText('Derived: 2')).toBeInTheDocument()) }) + + it('useStoreActions returns the stable actions bag without subscribing to state', () => { + const store = createStore({ count: 0 }, ({ set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + })) + + const { result, rerender } = renderHook(() => useStoreActions(store)) + const actions = result.current + + act(() => { + store.actions.inc() + }) + + rerender() + + expect(result.current).toBe(actions) + expect(store.state.count).toBe(1) + }) +}) + +describe('useStore', () => { + it('is a compatibility alias for useSelector', async () => { + const store = createStore(0) + + function Comp() { + const value = useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('supports atom sources through the deprecated alias', async () => { + const atom = createAtom(0) + + function Comp() { + const value = useStore(atom, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) }) describe('shallow', () => { diff --git a/packages/preact-store/tests/test.test-d.ts b/packages/preact-store/tests/test.test-d.ts index 3ce5bf4d..6d291fd1 100644 --- a/packages/preact-store/tests/test.test-d.ts +++ b/packages/preact-store/tests/test.test-d.ts @@ -1,15 +1,193 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + createStoreContext, + useAtom, + useCreateAtom, + useCreateStore, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src' +// eslint-disable-next-line no-duplicate-imports +import type { Atom, ReadonlyStore } from '@tanstack/store' -test('useStore works with derived state', () => { - const store = createStore(12) - const derived = createStore(() => store.state * 2) +test('useCreateAtom returns a writable atom for initial values', () => { + const atom = useCreateAtom(12) - const val = useStore(derived, (state) => { - expectTypeOf(state).toEqualTypeOf() - return state + expectTypeOf(atom.get()).toExtend() + expectTypeOf(atom.set).toBeFunction() +}) + +test('useCreateAtom returns a readonly atom for derived values', () => { + const atom = useCreateAtom(() => 12, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(atom.get()).toExtend() + expectTypeOf(atom).not.toHaveProperty('set') +}) + +test('useValue infers value from mutable and readonly atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toExtend() + expectTypeOf(useValue(readonlyAtom)).toExtend() + expectTypeOf(useValue(writableStore)).toExtend() + expectTypeOf(useValue(readonlyStore)).toExtend() + expectTypeOf( + useValue(writableAtom, { + compare: (prev, next) => prev === next, + }), + ).toExtend() +}) + +test('useSetValue preserves native atom and store setter contracts', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() + expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< + typeof writableStore.setState + >() + // @ts-expect-error readonly atoms cannot be set + useSetValue(readonlyAtom) + // @ts-expect-error readonly stores cannot be set + useSetValue(readonlyStore) +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + const [valueWithOptions] = useAtom(writableAtom, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(value).toExtend() + expectTypeOf(valueWithOptions).toExtend() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useCreateStore returns writable and readonly store types', () => { + const writableStore = useCreateStore(12) + const writableStoreWithActions = useCreateStore({ count: 0 }, ({ set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + })) + const readonlyStore = useCreateStore(() => 24) + + expectTypeOf(writableStore.state).toExtend() + expectTypeOf(writableStore.setState).toBeFunction() + expectTypeOf(writableStoreWithActions.state).toMatchObjectType<{ + count: number + }>() + expectTypeOf(writableStoreWithActions.actions.inc).toBeFunction() + expectTypeOf(readonlyStore.state).toExtend() + expectTypeOf(readonlyStore).not.toHaveProperty('setState') + + useCreateStore({ count: 0 }, () => ({ + // @ts-expect-error actions must be functions + asdf: 123, + inc: () => {}, + })) +}) + +test('useSelector infers state and selected types for stores', () => { + const baseStore = createStore(12) + const derivedStore = createStore(() => { + return { val: baseStore.state * 2 } + }) + + const val = useSelector(derivedStore, (state) => { + expectTypeOf(state).toMatchObjectType<{ val: number }>() + return state.val }) + const valWithOptions = useSelector(derivedStore, (state) => state.val, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(val).toExtend() + expectTypeOf(valWithOptions).toExtend() +}) + +test('useSelector infers state and selected types for atoms', () => { + const atom = createAtom({ val: 12 }) + + const val = useSelector(atom, (state) => { + expectTypeOf(state).toMatchObjectType<{ val: number }>() + return state.val + }) + + expectTypeOf(val).toExtend() +}) + +test('useStore matches useSelector types for compatibility', () => { + const baseStore = createStore(12) + const derivedStore = createStore(() => { + return { val: baseStore.state * 2 } + }) + + const selectorValue = useSelector(derivedStore, (state) => state.val) + const compatValue = useStore( + derivedStore, + (state) => state.val, + (prev, next) => prev === next, + ) + + expectTypeOf(selectorValue).toExtend() + expectTypeOf(compatValue).toExtend() +}) + +test('useStoreActions infers the action bag from writable stores', () => { + const store = createStore({ count: 0 }, ({ get, set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })) + + const actions = useStoreActions(store) + + expectTypeOf(actions.inc).toBeFunction() + expectTypeOf(actions.current()).toExtend() + + const plainStore = createStore(12) + expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() + + const readonlyStore = createStore(() => 24) + // @ts-expect-error readonly stores do not expose actions + useStoreActions(readonlyStore) +}) + +test('createStoreContext preserves keyed atom and store types', () => { + const countAtom = createAtom(12) + const readonlySource = createStore(() => ({ value: 24 })) + const storeFactory = createStoreContext<{ + countAtom: typeof countAtom + readonlyStore: typeof readonlySource + }>() + const contextValue = storeFactory.useStoreContext() + + expectTypeOf(contextValue.countAtom).toExtend>() + expectTypeOf(contextValue.countAtom.set).toBeFunction() + expectTypeOf(useSetValue(contextValue.countAtom)).toBeFunction() + + const [value, setValue] = useAtom(contextValue.countAtom) + expectTypeOf(value).toExtend() + expectTypeOf(setValue).toBeFunction() + + const readonlyStore = contextValue.readonlyStore + expectTypeOf(readonlyStore).toExtend>() + expectTypeOf(readonlyStore).not.toHaveProperty('setState') - expectTypeOf(val).toEqualTypeOf() + const selected = useSelector(readonlyStore, (state) => state.value) + expectTypeOf(selected).toExtend() }) diff --git a/packages/react-store/src/useStore.ts b/packages/react-store/src/useStore.ts index 7257eac1..b08021a1 100644 --- a/packages/react-store/src/useStore.ts +++ b/packages/react-store/src/useStore.ts @@ -3,6 +3,11 @@ import { useSelector } from './useSelector' /** * Deprecated alias for {@link useSelector}. * + * @example + * ```tsx + * const count = useStore(counterStore, (state) => state.count) + * ``` + * * @deprecated Use `useSelector` instead. */ export const useStore = ( diff --git a/packages/solid-store/src/index.tsx b/packages/solid-store/src/index.tsx index 6f8cee03..e03d889a 100644 --- a/packages/solid-store/src/index.tsx +++ b/packages/solid-store/src/index.tsx @@ -1,84 +1,13 @@ -import { createSignal, onCleanup } from 'solid-js' -import type { Accessor } from 'solid-js' -import type { Atom, ReadonlyAtom } from '@tanstack/store' - export * from '@tanstack/store' -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): Accessor { - const [signal, setSignal] = createSignal(selector(store.get())) - const equal = options.equal ?? shallow - - const unsub = store.subscribe((s) => { - const data = selector(s) - if (equal(signal(), data)) { - return - } - setSignal(() => data) - }).unsubscribe - - onCleanup(() => { - unsub() - }) - - return signal -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } +export * from './useSetValue' +export * from './useStoreActions' - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } +export * from './useValue' +export * from './useSelector' - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +// comparators +export * from './shallow' diff --git a/packages/solid-store/src/shallow.ts b/packages/solid-store/src/shallow.ts new file mode 100644 index 00000000..75ff1634 --- /dev/null +++ b/packages/solid-store/src/shallow.ts @@ -0,0 +1,57 @@ +export function shallow(objA: T, objB: T) { + if (Object.is(objA, objB)) { + return true + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + if (objA instanceof Map && objB instanceof Map) { + if (objA.size !== objB.size) return false + for (const [k, v] of objA) { + if (!objB.has(k) || !Object.is(v, objB.get(k))) return false + } + return true + } + + if (objA instanceof Set && objB instanceof Set) { + if (objA.size !== objB.size) return false + for (const v of objA) { + if (!objB.has(v)) return false + } + return true + } + + if (objA instanceof Date && objB instanceof Date) { + if (objA.getTime() !== objB.getTime()) return false + return true + } + + const keysA = getOwnKeys(objA) + if (keysA.length !== getOwnKeys(objB).length) { + return false + } + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || + !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) + ) { + return false + } + } + return true +} + +function getOwnKeys(obj: object): Array { + return (Object.keys(obj) as Array).concat( + Object.getOwnPropertySymbols(obj), + ) +} diff --git a/packages/solid-store/src/useAtom.ts b/packages/solid-store/src/useAtom.ts new file mode 100644 index 00000000..41f82f56 --- /dev/null +++ b/packages/solid-store/src/useAtom.ts @@ -0,0 +1,32 @@ +import { useSetValue } from './useSetValue' +import { useValue } from './useValue' +import type { Accessor } from 'solid-js' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom accessor together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```tsx + * const [count, setCount] = useAtom(countAtom) + * + * return ( + * + * ) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [Accessor, Atom['set']] { + const value = useValue(atom, options) + const setValue = useSetValue(atom) + + return [value, setValue] +} diff --git a/packages/solid-store/src/useSelector.ts b/packages/solid-store/src/useSelector.ts new file mode 100644 index 00000000..720c5579 --- /dev/null +++ b/packages/solid-store/src/useSelector.ts @@ -0,0 +1,57 @@ +import { createSignal, onCleanup } from 'solid-js' +import type { Accessor } from 'solid-js' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary Solid read hook for TanStack Store. It returns a Solid + * accessor so consumers can read the selected value reactively. + * + * @example + * ```tsx + * const count = useSelector(counterStore, (state) => state.count) + * + * return

{count()}

+ * ``` + * + * @example + * ```tsx + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): Accessor { + const compare = options?.compare ?? defaultCompare + const [signal, setSignal] = createSignal(selector(source.get()), { + equals: compare, + }) + + const unsubscribe = source.subscribe((snapshot) => { + setSignal(() => selector(snapshot)) + }).unsubscribe + + onCleanup(() => { + unsubscribe() + }) + + return signal +} diff --git a/packages/solid-store/src/useSetValue.ts b/packages/solid-store/src/useSetValue.ts new file mode 100644 index 00000000..28886d68 --- /dev/null +++ b/packages/solid-store/src/useSetValue.ts @@ -0,0 +1,39 @@ +import type { Atom, Store, StoreActionMap } from '@tanstack/store' + +/** + * Returns a stable setter for a writable atom or store. + * + * Writable atoms preserve their native `set` contract. Writable stores + * preserve their native `setState` contract. + * + * @example + * ```tsx + * const setCount = useSetValue(countAtom) + * setCount((prev) => prev + 1) + * ``` + * + * @example + * ```tsx + * const setState = useSetValue(counterStore) + * setState((state) => ({ ...state, count: state.count + 1 })) + * ``` + */ +export function useSetValue(source: Atom): Atom['set'] +export function useSetValue( + source: Store, +): Store['setState'] +export function useSetValue( + source: Atom | Store, +) { + return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { + if ('setState' in source) { + source.setState(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + if (typeof valueOrUpdater === 'function') { + source.set(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + source.set(valueOrUpdater) + } + } + }) as Atom['set'] | Store['setState'] +} diff --git a/packages/solid-store/src/useStore.ts b/packages/solid-store/src/useStore.ts new file mode 100644 index 00000000..05149a97 --- /dev/null +++ b/packages/solid-store/src/useStore.ts @@ -0,0 +1,24 @@ +import { useSelector } from './useSelector' +import type { Accessor } from 'solid-js' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```tsx + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected = (value) => + value as unknown as TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +): Accessor => useSelector(source, selector, { compare }) diff --git a/packages/solid-store/src/useStoreActions.ts b/packages/solid-store/src/useStoreActions.ts new file mode 100644 index 00000000..773f6ace --- /dev/null +++ b/packages/solid-store/src/useStoreActions.ts @@ -0,0 +1,19 @@ +import type { Store, StoreActionMap } from '@tanstack/store' + +/** + * Returns the stable actions bag from a writable store created with actions. + * + * Use this when a component only needs to call store actions and should not + * subscribe to store state. + * + * @example + * ```tsx + * const actions = useStoreActions(counterStore) + * actions.increment() + * ``` + */ +export function useStoreActions( + store: Store, +): TActions { + return store.actions +} diff --git a/packages/solid-store/src/useValue.ts b/packages/solid-store/src/useValue.ts new file mode 100644 index 00000000..a6ebbd63 --- /dev/null +++ b/packages/solid-store/src/useValue.ts @@ -0,0 +1,33 @@ +import { useSelector } from './useSelector' +import type { Accessor } from 'solid-js' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value accessor. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when the + * component needs the entire current value from a source. + * + * @example + * ```tsx + * const count = useValue(countAtom) + * + * return

{count()}

+ * ``` + * + * @example + * ```tsx + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): Accessor { + return useSelector(source, (value) => value, options) +} diff --git a/packages/solid-store/tests/index.test.tsx b/packages/solid-store/tests/index.test.tsx index 082521c0..08fcfff9 100644 --- a/packages/solid-store/tests/index.test.tsx +++ b/packages/solid-store/tests/index.test.tsx @@ -1,55 +1,313 @@ -import { describe, expect, it } from 'vitest' -import { render, renderHook } from '@solidjs/testing-library' -import { createStore } from '@tanstack/store' -import { useStore } from '../src/index' +import { describe, expect, it, test, vi } from 'vitest' +import { renderHook } from '@solidjs/testing-library' +import { createAtom, createStore } from '@tanstack/store' +import { + shallow, + useAtom, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src/index' -describe('useStore', () => { - it.todo('allows us to select state using a selector', () => { +describe('atom hooks', () => { + it('useValue reads mutable atom state and updates when changed', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useValue(atom)) + + expect(result()).toBe(0) + + atom.set((prev) => prev + 1) + + expect(result()).toBe(1) + }) + + it('useSetValue updates atoms by value and updater', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useSetValue(atom)) + + result(1) + expect(atom.get()).toBe(1) + + result((prev) => prev + 1) + expect(atom.get()).toBe(2) + }) + + it('useAtom returns the current accessor and setter', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useAtom(atom)) + + expect(result[0]()).toBe(0) + + result[1]((prev) => prev + 5) + + expect(result[0]()).toBe(5) + }) +}) + +describe('store hooks', () => { + it('useSelector allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1, }) - function Comp() { - const storeVal = useStore(store, (state) => state.select) + const { result } = renderHook(() => + useSelector(store, (state) => state.select), + ) + + expect(result()).toBe(0) + }) + + it('useValue reads writable and readonly store state', () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + const { result: writableValue } = renderHook(() => useValue(baseStore)) + const { result: readonlyValue } = renderHook(() => useValue(readonlyStore)) - return

Store: {storeVal()}

- } + expect(writableValue()).toBe(1) + expect(readonlyValue().value).toBe(2) - const { getByText } = render(() => ) - expect(getByText('Store: 0')).toBeInTheDocument() + baseStore.setState((prev) => prev + 1) + + expect(writableValue()).toBe(2) + expect(readonlyValue().value).toBe(4) }) - it('allows us to select state using a selector', () => { + it('useSelector only updates the accessor when selected state changes', () => { const store = createStore({ select: 0, ignored: 1, }) + const renderSpy = vi.fn() + + const { result } = renderHook(() => { + const value = useSelector(store, (state) => state.select) + renderSpy() + return value + }) + + expect(result()).toBe(0) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + ...prev, + ignored: prev.ignored + 1, + })) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + ...prev, + select: prev.select + 1, + })) + expect(result()).toBe(1) + expect(renderSpy).toHaveBeenCalledTimes(1) + }) + + it('useSelector respects custom compare', () => { + const store = createStore({ + array: [ + { select: 0, ignore: 1 }, + { select: 0, ignore: 1 }, + ], + }) + const renderSpy = vi.fn() + const { result } = renderHook(() => { + const value = useSelector( + store, + (state) => state.array.map(({ ignore, ...rest }) => rest), + { + compare: (prev, next) => + JSON.stringify(prev) === JSON.stringify(next), + }, + ) + renderSpy() + return value + }) + + expect(result().map((item) => item.select)).toEqual([0, 0]) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + array: prev.array.map((item) => ({ + ...item, + ignore: item.ignore + 1, + })), + })) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + array: prev.array.map((item) => ({ + ...item, + select: item.select + 1, + })), + })) + expect(result().map((item) => item.select)).toEqual([1, 1]) + expect(renderSpy).toHaveBeenCalledTimes(1) + }) + + it('useSelector works with mounted derived stores', () => { + const store = createStore(0) + const derived = createStore(() => ({ val: store.state * 2 })) const { result } = renderHook(() => - useStore(store, (state) => state.select), + useSelector(derived, (state) => state.val), ) expect(result()).toBe(0) + + store.setState((prev) => prev + 1) + + expect(result()).toBe(2) }) - it('updates accessor value when state is updated', () => { + it('useSetValue updates stores by updater', () => { const store = createStore(0) + const { result } = renderHook(() => useSetValue(store)) - const { result } = renderHook(() => useStore(store)) + result((prev) => prev + 1) + expect(store.state).toBe(1) + }) + + it('useStoreActions returns the stable actions bag', () => { + const store = createStore({ count: 0 }, ({ set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + })) + + const { result } = renderHook(() => useStoreActions(store)) + const actions = result + + actions.inc() + + expect(result).toBe(actions) + expect(store.state.count).toBe(1) + }) +}) + +describe('useStore', () => { + it('is a compatibility alias for useSelector', () => { + const store = createStore(0) + const { result } = renderHook(() => useStore(store, (state) => state)) + + expect(result()).toBe(0) store.setState((prev) => prev + 1) expect(result()).toBe(1) }) - it('updates when date changes', () => { - const store = createStore(new Date('2025-03-29T21:06:30.401Z')) + it('supports atom sources through the deprecated alias', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useStore(atom, (state) => state)) + + expect(result()).toBe(0) + + atom.set((prev) => prev + 1) + + expect(result()).toBe(1) + }) +}) + +describe('shallow', () => { + test('should return true for shallowly equal objects', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 1, b: 'hello' } + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for objects with different values', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 2, b: 'world' } + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for objects with different keys', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 1, c: 'world' } + // @ts-expect-error + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for objects with different structures', () => { + const objA = { a: 1, b: 'hello' } + const objB = [1, 'hello'] + // @ts-expect-error + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for one object being null', () => { + const objA = { a: 1, b: 'hello' } + const objB = null + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for one object being undefined', () => { + const objA = { a: 1, b: 'hello' } + const objB = undefined + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for two null objects', () => { + const objA = null + const objB = null + expect(shallow(objA, objB)).toBe(true) + }) - const { result } = renderHook(() => useStore(store)) + test('should return false for objects with different types', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: '1', b: 'hello' } + // @ts-expect-error + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for shallow equal objects with symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 1 } + expect(shallow(objA, objB)).toBe(true) + }) - store.setState(() => new Date('2025-03-29T21:06:40.401Z')) + test('should return false for shallow different values for symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 2 } + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for shallowly equal maps', () => { + const objA = new Map([['1', 'hello']]) + const objB = new Map([['1', 'hello']]) + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for maps with different values', () => { + const objA = new Map([['1', 'hello']]) + const objB = new Map([['1', 'world']]) + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for shallowly equal sets', () => { + const objA = new Set([1]) + const objB = new Set([1]) + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for sets with different values', () => { + const objA = new Set([1]) + const objB = new Set([2]) + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for dates with different values', () => { + const objA = new Date('2025-04-10T14:48:00') + const objB = new Date('2025-04-10T14:58:00') + expect(shallow(objA, objB)).toBe(false) + }) - expect(result()).toStrictEqual(new Date('2025-03-29T21:06:40.401Z')) + test('should return true for equal dates', () => { + const objA = new Date('2025-02-10') + const objB = new Date('2025-02-10') + expect(shallow(objA, objB)).toBe(true) }) }) diff --git a/packages/solid-store/tests/test.test-d.ts b/packages/solid-store/tests/test.test-d.ts index e3da48b4..639f8346 100644 --- a/packages/solid-store/tests/test.test-d.ts +++ b/packages/solid-store/tests/test.test-d.ts @@ -1,16 +1,92 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + useAtom, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src' import type { Accessor } from 'solid-js' +import type { Atom } from '@tanstack/store' -test('useStore works with derived state', () => { +test('useSelector works with derived state', () => { const store = createStore(12) const derived = createStore(() => store.state * 2) - const val = useStore(derived, (state) => { + const val = useSelector(derived, (state) => { expectTypeOf(state).toEqualTypeOf() return state }) expectTypeOf(val).toEqualTypeOf>() }) + +test('useValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toEqualTypeOf>() + expectTypeOf(useValue(readonlyAtom)).toEqualTypeOf>() + expectTypeOf(useValue(writableStore)).toEqualTypeOf>() + expectTypeOf(useValue(readonlyStore)).toEqualTypeOf>() +}) + +test('useSetValue preserves native setter contracts', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() + expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< + typeof writableStore.setState + >() + // @ts-expect-error readonly atoms cannot be set + useSetValue(readonlyAtom) + // @ts-expect-error readonly stores cannot be set + useSetValue(readonlyStore) +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useStore matches useSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = useSelector(store, (state) => state) + const compatValue = useStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf>() + expectTypeOf(compatValue).toEqualTypeOf>() +}) + +test('useStoreActions infers the action bag from writable stores', () => { + const store = createStore({ count: 0 }, ({ get, set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })) + + const actions = useStoreActions(store) + + expectTypeOf(actions.inc).toBeFunction() + expectTypeOf(actions.current()).toExtend() + + const plainStore = createStore(12) + expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() + + const readonlyStore = createStore(() => 24) + // @ts-expect-error readonly stores do not expose actions + useStoreActions(readonlyStore) +}) diff --git a/packages/svelte-store/src/index.svelte.ts b/packages/svelte-store/src/index.svelte.ts index ea4df8ce..cf23d20c 100644 --- a/packages/svelte-store/src/index.svelte.ts +++ b/packages/svelte-store/src/index.svelte.ts @@ -1,24 +1,54 @@ -import type { Atom, ReadonlyAtom } from '@tanstack/store' +import type { + Atom, + ReadonlyAtom, + ReadonlyStore, + Store, + StoreActionMap, +} from '@tanstack/store' export * from '@tanstack/store' -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean } -export function useStore>( - store: Atom | ReadonlyAtom, +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and exposes it through a + * rune-friendly holder object. + * + * Read the selected value from `.current`. + * + * @example + * ```ts + * const count = useSelector(counterStore, (state) => state.count) + * console.log(count.current) + * ``` + * + * @example + * ```ts + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector>( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, + options: UseSelectorOptions = {}, ): { readonly current: TSelected } { - const equal = options.equal ?? shallow - let slice = $state(selector(store.get())) + const compare = options.compare ?? defaultCompare + let slice = $state(selector(source.get())) $effect(() => { - const unsub = store.subscribe((s) => { + const unsub = source.subscribe((s) => { const data = selector(s) - if (equal(slice, data)) { + if (compare(slice, data)) { return } slice = data @@ -34,6 +64,132 @@ export function useStore>( } } +/** + * Subscribes to an atom or store and returns its whole current value through a + * rune-friendly holder object. + * + * @example + * ```ts + * const count = useValue(countAtom) + * console.log(count.current) + * ``` + * + * @example + * ```ts + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): { readonly current: TValue } { + return useSelector(source, (value) => value, options) +} + +/** + * Returns a stable setter for a writable atom or store. + * + * Writable atoms preserve their native `set` contract. Writable stores + * preserve their native `setState` contract. + * + * @example + * ```ts + * const setCount = useSetValue(countAtom) + * setCount((prev) => prev + 1) + * ``` + * + * @example + * ```ts + * const setState = useSetValue(counterStore) + * setState((state) => ({ ...state, count: state.count + 1 })) + * ``` + */ +export function useSetValue(source: Atom): Atom['set'] +export function useSetValue( + source: Store, +): Store['setState'] +export function useSetValue( + source: Atom | Store, +) { + return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { + if ('setState' in source) { + source.setState(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + if (typeof valueOrUpdater === 'function') { + source.set(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + source.set(valueOrUpdater) + } + } + }) as Atom['set'] | Store['setState'] +} + +/** + * Returns the current atom holder together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * const [count, setCount] = useAtom(countAtom) + * setCount((prev) => prev + 1) + * console.log(count.current) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [{ readonly current: TValue }, Atom['set']] { + const value = useValue(atom, options) + const setValue = useSetValue(atom) + + return [value, setValue] +} + +/** + * Returns the stable actions bag from a writable store created with actions. + * + * Use this when a component only needs to call store actions and should not + * subscribe to store state. + * + * @example + * ```ts + * const actions = useStoreActions(counterStore) + * actions.increment() + * ``` + */ +export function useStoreActions( + store: Store, +): TActions { + return store.actions +} + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```ts + * const count = useStore(counterStore, (state) => state.count) + * console.log(count.current) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = >( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + selector: (state: NoInfer) => TSelected = (d) => d as any, + compare?: (a: TSelected, b: TSelected) => boolean, +) => useSelector(source, selector, { compare }) + export function shallow(objA: T, objB: T) { if (Object.is(objA, objB)) { return true diff --git a/packages/svelte-store/tests/BaseStore.test.svelte b/packages/svelte-store/tests/BaseStore.test.svelte index 529e410f..aef07158 100644 --- a/packages/svelte-store/tests/BaseStore.test.svelte +++ b/packages/svelte-store/tests/BaseStore.test.svelte @@ -1,13 +1,13 @@

Store: {storeVal.current}

diff --git a/packages/svelte-store/tests/Render.test.svelte b/packages/svelte-store/tests/Render.test.svelte index 7700c555..baf6381a 100644 --- a/packages/svelte-store/tests/Render.test.svelte +++ b/packages/svelte-store/tests/Render.test.svelte @@ -1,14 +1,14 @@ + +
+

Value: {value.current}

+

Readonly: {readonlyValue.current.value}

+ +
diff --git a/packages/svelte-store/tests/index.test.ts b/packages/svelte-store/tests/index.test.ts index 24f7a3dd..f4bb1b6a 100644 --- a/packages/svelte-store/tests/index.test.ts +++ b/packages/svelte-store/tests/index.test.ts @@ -1,13 +1,15 @@ import { describe, expect, it, test } from 'vitest' import { render, waitFor } from '@testing-library/svelte' import { userEvent } from '@testing-library/user-event' -import { shallow } from '../src/index.svelte.js' +import { createStore } from '@tanstack/store' +import { shallow, useStoreActions } from '../src/index.svelte.js' import TestBaseStore from './BaseStore.test.svelte' import TestRerender from './Render.test.svelte' +import TestValue from './Value.test.svelte' const user = userEvent.setup() -describe('useStore', () => { +describe('useSelector', () => { it('allows us to select state using a selector', () => { const { getByText } = render(TestBaseStore) expect(getByText('Store: 0')).toBeInTheDocument() @@ -26,6 +28,28 @@ describe('useStore', () => { await user.click(getByText('Update ignored')) expect(getByText('Number rendered: 2')).toBeInTheDocument() }) + + it('useValue reads writable and readonly store state', async () => { + const { getByText } = render(TestValue) + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) + + it('useStoreActions returns the stable actions bag', () => { + const store = createStore({ count: 0 }, ({ set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + })) + + const actions = useStoreActions(store) + actions.inc() + + expect(store.state.count).toBe(1) + }) }) describe('shallow', () => { diff --git a/packages/svelte-store/tests/test.test-d.ts b/packages/svelte-store/tests/test.test-d.ts new file mode 100644 index 00000000..be1748b3 --- /dev/null +++ b/packages/svelte-store/tests/test.test-d.ts @@ -0,0 +1,99 @@ +import { expectTypeOf, test } from 'vitest' +import { createAtom, createStore } from '@tanstack/store' +import { + useAtom, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src/index.svelte.js' +import type { Atom } from '@tanstack/store' + +test('useSelector works with derived state', () => { + const store = createStore(12) + const derived = createStore(() => store.state * 2) + + const val = useSelector(derived, (state) => { + expectTypeOf(state).toEqualTypeOf() + return state + }) + + expectTypeOf(val).toEqualTypeOf<{ readonly current: number }>() +}) + +test('useValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toEqualTypeOf<{ + readonly current: number + }>() + expectTypeOf(useValue(readonlyAtom)).toEqualTypeOf<{ + readonly current: number + }>() + expectTypeOf(useValue(writableStore)).toEqualTypeOf<{ + readonly current: number + }>() + expectTypeOf(useValue(readonlyStore)).toEqualTypeOf<{ + readonly current: number + }>() +}) + +test('useSetValue preserves native setter contracts', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() + expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< + typeof writableStore.setState + >() + // @ts-expect-error readonly atoms cannot be set + useSetValue(readonlyAtom) + // @ts-expect-error readonly stores cannot be set + useSetValue(readonlyStore) +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf<{ readonly current: number }>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useStore matches useSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = useSelector(store, (state) => state) + const compatValue = useStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf<{ readonly current: number }>() + expectTypeOf(compatValue).toEqualTypeOf<{ readonly current: number }>() +}) + +test('useStoreActions infers the action bag from writable stores', () => { + const store = createStore({ count: 0 }, ({ get, set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })) + + const actions = useStoreActions(store) + + expectTypeOf(actions.inc).toBeFunction() + expectTypeOf(actions.current()).toExtend() + + const plainStore = createStore(12) + expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() + + const readonlyStore = createStore(() => 24) + // @ts-expect-error readonly stores do not expose actions + useStoreActions(readonlyStore) +}) diff --git a/packages/vue-store/src/index.ts b/packages/vue-store/src/index.ts index e8c1107c..e03d889a 100644 --- a/packages/vue-store/src/index.ts +++ b/packages/vue-store/src/index.ts @@ -1,90 +1,13 @@ -import { readonly, ref, toRaw, watch } from 'vue-demi' -import type { Atom, ReadonlyAtom } from '@tanstack/store' -import type { Ref } from 'vue-demi' - export * from '@tanstack/store' -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): Readonly> { - const slice = ref(selector(store.get())) as Ref - const equal = options.equal ?? shallow - - watch( - () => store, - (value, _oldValue, onCleanup) => { - const unsub = value.subscribe((s) => { - const data = selector(s) - if (equal(toRaw(slice.value), data)) { - return - } - slice.value = data - }).unsubscribe - - onCleanup(() => { - unsub() - }) - }, - { immediate: true }, - ) - - return readonly(slice) as never -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } +export * from './useSetValue' +export * from './useStoreActions' - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } +export * from './useValue' +export * from './useSelector' - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +// comparators +export * from './shallow' diff --git a/packages/vue-store/src/shallow.ts b/packages/vue-store/src/shallow.ts new file mode 100644 index 00000000..75ff1634 --- /dev/null +++ b/packages/vue-store/src/shallow.ts @@ -0,0 +1,57 @@ +export function shallow(objA: T, objB: T) { + if (Object.is(objA, objB)) { + return true + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + if (objA instanceof Map && objB instanceof Map) { + if (objA.size !== objB.size) return false + for (const [k, v] of objA) { + if (!objB.has(k) || !Object.is(v, objB.get(k))) return false + } + return true + } + + if (objA instanceof Set && objB instanceof Set) { + if (objA.size !== objB.size) return false + for (const v of objA) { + if (!objB.has(v)) return false + } + return true + } + + if (objA instanceof Date && objB instanceof Date) { + if (objA.getTime() !== objB.getTime()) return false + return true + } + + const keysA = getOwnKeys(objA) + if (keysA.length !== getOwnKeys(objB).length) { + return false + } + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || + !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) + ) { + return false + } + } + return true +} + +function getOwnKeys(obj: object): Array { + return (Object.keys(obj) as Array).concat( + Object.getOwnPropertySymbols(obj), + ) +} diff --git a/packages/vue-store/src/useAtom.ts b/packages/vue-store/src/useAtom.ts new file mode 100644 index 00000000..e49ef39f --- /dev/null +++ b/packages/vue-store/src/useAtom.ts @@ -0,0 +1,29 @@ +import { useSetValue } from './useSetValue' +import { useValue } from './useValue' +import type { Ref } from 'vue-demi' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom ref together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * const [count, setCount] = useAtom(countAtom) + * + * setCount((prev) => prev + 1) + * console.log(count.value) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [Readonly>, Atom['set']] { + const value = useValue(atom, options) + const setValue = useSetValue(atom) + + return [value, setValue] +} diff --git a/packages/vue-store/src/useSelector.ts b/packages/vue-store/src/useSelector.ts new file mode 100644 index 00000000..a557a960 --- /dev/null +++ b/packages/vue-store/src/useSelector.ts @@ -0,0 +1,57 @@ +import { onScopeDispose, readonly, shallowRef, toRaw } from 'vue-demi' +import type { Ref } from 'vue-demi' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary Vue read hook for TanStack Store. It returns a readonly + * ref containing the selected value. + * + * @example + * ```ts + * const count = useSelector(counterStore, (state) => state.count) + * console.log(count.value) + * ``` + * + * @example + * ```ts + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): Readonly> { + const compare = options?.compare ?? defaultCompare + const slice = shallowRef(selector(source.get())) as Ref + const unsubscribe = source.subscribe((snapshot) => { + const selected = selector(snapshot) + if (compare(toRaw(slice.value), selected)) { + return + } + slice.value = selected + }).unsubscribe + + onScopeDispose(() => { + unsubscribe() + }) + + return readonly(slice) as Readonly> +} diff --git a/packages/vue-store/src/useSetValue.ts b/packages/vue-store/src/useSetValue.ts new file mode 100644 index 00000000..9cf8de17 --- /dev/null +++ b/packages/vue-store/src/useSetValue.ts @@ -0,0 +1,39 @@ +import type { Atom, Store, StoreActionMap } from '@tanstack/store' + +/** + * Returns a stable setter for a writable atom or store. + * + * Writable atoms preserve their native `set` contract. Writable stores + * preserve their native `setState` contract. + * + * @example + * ```ts + * const setCount = useSetValue(countAtom) + * setCount((prev) => prev + 1) + * ``` + * + * @example + * ```ts + * const setState = useSetValue(counterStore) + * setState((state) => ({ ...state, count: state.count + 1 })) + * ``` + */ +export function useSetValue(source: Atom): Atom['set'] +export function useSetValue( + source: Store, +): Store['setState'] +export function useSetValue( + source: Atom | Store, +) { + return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { + if ('setState' in source) { + source.setState(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + if (typeof valueOrUpdater === 'function') { + source.set(valueOrUpdater as (prevVal: TValue) => TValue) + } else { + source.set(valueOrUpdater) + } + } + }) as Atom['set'] | Store['setState'] +} diff --git a/packages/vue-store/src/useStore.ts b/packages/vue-store/src/useStore.ts new file mode 100644 index 00000000..2883074c --- /dev/null +++ b/packages/vue-store/src/useStore.ts @@ -0,0 +1,23 @@ +import { useSelector } from './useSelector' +import type { Ref } from 'vue-demi' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```ts + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +): Readonly> => useSelector(source, selector, { compare }) diff --git a/packages/vue-store/src/useStoreActions.ts b/packages/vue-store/src/useStoreActions.ts new file mode 100644 index 00000000..a6ab7fc2 --- /dev/null +++ b/packages/vue-store/src/useStoreActions.ts @@ -0,0 +1,19 @@ +import type { Store, StoreActionMap } from '@tanstack/store' + +/** + * Returns the stable actions bag from a writable store created with actions. + * + * Use this when a component only needs to call store actions and should not + * subscribe to store state. + * + * @example + * ```ts + * const actions = useStoreActions(counterStore) + * actions.increment() + * ``` + */ +export function useStoreActions( + store: Store, +): TActions { + return store.actions +} diff --git a/packages/vue-store/src/useValue.ts b/packages/vue-store/src/useValue.ts new file mode 100644 index 00000000..ef24720b --- /dev/null +++ b/packages/vue-store/src/useValue.ts @@ -0,0 +1,32 @@ +import { useSelector } from './useSelector' +import type { Ref } from 'vue-demi' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value ref. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when the + * component needs the entire current value from a source. + * + * @example + * ```ts + * const count = useValue(countAtom) + * console.log(count.value) + * ``` + * + * @example + * ```ts + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): Readonly> { + return useSelector(source, (value) => value, options) +} diff --git a/packages/vue-store/tests/index.test.tsx b/packages/vue-store/tests/index.test.tsx index a81df786..ab5ac048 100644 --- a/packages/vue-store/tests/index.test.tsx +++ b/packages/vue-store/tests/index.test.tsx @@ -1,13 +1,77 @@ import { describe, expect, it, test, vi } from 'vitest' import { defineComponent, h } from 'vue-demi' import { render, waitFor } from '@testing-library/vue' -import { createStore } from '@tanstack/store' +import { createAtom, createStore } from '@tanstack/store' import { userEvent } from '@testing-library/user-event' -import { shallow, useStore } from '../src/index' +import { + shallow, + useAtom, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src/index' const user = userEvent.setup() -describe('useStore', () => { +describe('atom hooks', () => { + it('useValue reads mutable atom state and rerenders when updated', async () => { + const atom = createAtom(0) + + const Comp = defineComponent(() => { + const value = useValue(atom) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => atom.set((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('useAtom returns the current ref and setter', async () => { + const atom = createAtom(0) + + const Comp = defineComponent(() => { + const [value, setValue] = useAtom(atom) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => setValue((prev) => prev + 5), + }, + 'Add 5', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Add 5')) + + await waitFor(() => expect(getByText('Value: 5')).toBeInTheDocument()) + }) +}) + +describe('store hooks', () => { it('allows us to select state using a selector', () => { const store = createStore({ select: 0, @@ -15,7 +79,7 @@ describe('useStore', () => { }) const Comp = defineComponent(() => { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) return () => h('p', `Store: ${storeVal.value}`) }) @@ -24,6 +88,38 @@ describe('useStore', () => { expect(getByText('Store: 0')).toBeInTheDocument() }) + it('useValue reads writable and readonly store state', async () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + const Comp = defineComponent(() => { + const value = useValue(baseStore) + const readonlyValue = useValue(readonlyStore) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h('p', `Readonly: ${readonlyValue.value.value}`), + h( + 'button', + { + onClick: () => baseStore.setState((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) + it('only triggers a re-render when selector state is updated', async () => { const store = createStore({ select: 0, @@ -31,8 +127,7 @@ describe('useStore', () => { }) const Comp = defineComponent(() => { - const storeVal = useStore(store, (state) => state.select) - + const storeVal = useSelector(store, (state) => state.select) const fn = vi.fn() return () => { @@ -78,6 +173,201 @@ describe('useStore', () => { await user.click(getByText('Update ignored')) expect(getByText('Number rendered: 2')).toBeInTheDocument() }) + + it('useSelector allows specifying a custom equality function', async () => { + const store = createStore({ + array: [ + { select: 0, ignore: 1 }, + { select: 0, ignore: 1 }, + ], + }) + + const Comp = defineComponent(() => { + const storeVal = useSelector( + store, + (state) => state.array.map(({ ignore, ...rest }) => rest), + { + compare: (prev, next) => + JSON.stringify(prev) === JSON.stringify(next), + }, + ) + const fn = vi.fn() + + return () => { + fn() + const value = storeVal.value + .map((item) => item.select) + .reduce((total, num) => total + num, 0) + + return h('div', [ + h('p', `Number rendered: ${fn.mock.calls.length}`), + h('p', `Store: ${value}`), + h( + 'button', + { + onClick: () => + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + select: item.select + 5, + })), + })), + }, + 'Update select', + ), + h( + 'button', + { + onClick: () => + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + ignore: item.ignore + 2, + })), + })), + }, + 'Update ignored', + ), + ]) + } + }) + + const { getByText } = render(Comp) + expect(getByText('Store: 0')).toBeInTheDocument() + expect(getByText('Number rendered: 1')).toBeInTheDocument() + + await user.click(getByText('Update select')) + + await waitFor(() => expect(getByText('Store: 10')).toBeInTheDocument()) + expect(getByText('Number rendered: 2')).toBeInTheDocument() + + await user.click(getByText('Update ignored')) + expect(getByText('Number rendered: 2')).toBeInTheDocument() + }) + + it('useSelector works with mounted derived stores', async () => { + const store = createStore(0) + const derived = createStore(() => ({ val: store.state * 2 })) + + const Comp = defineComponent(() => { + const derivedVal = useSelector(derived, (state) => state.val) + + return () => + h('div', [ + h('p', `Derived: ${derivedVal.value}`), + h( + 'button', + { + onClick: () => store.setState((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Derived: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Derived: 2')).toBeInTheDocument()) + }) + + it('useSetValue updates stores by updater', async () => { + const store = createStore(0) + + const Comp = defineComponent(() => { + const setValue = useSetValue(store) + + return () => + h('button', { onClick: () => setValue((prev) => prev + 1) }, 'Update') + }) + + const { getByText } = render(Comp) + + await user.click(getByText('Update')) + + expect(store.state).toBe(1) + }) + + it('useStoreActions returns the stable actions bag without subscribing to state', async () => { + const store = createStore({ count: 0 }, ({ set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + })) + + const Comp = defineComponent(() => { + const actions = useStoreActions(store) + + return () => + h( + 'button', + { onClick: () => actions.inc() }, + `Count: ${store.state.count}`, + ) + }) + + const { getByText } = render(Comp) + + await user.click(getByText('Count: 0')) + + expect(store.state.count).toBe(1) + }) +}) + +describe('useStore', () => { + it('is a compatibility alias for useSelector', async () => { + const store = createStore(0) + + const Comp = defineComponent(() => { + const value = useStore(store, (state) => state) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => store.setState((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('supports atom sources through the deprecated alias', async () => { + const atom = createAtom(0) + + const Comp = defineComponent(() => { + const value = useStore(atom, (state) => state) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => atom.set((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) }) describe('shallow', () => { @@ -132,6 +422,20 @@ describe('shallow', () => { expect(shallow(objA, objB)).toBe(false) }) + test('should return true for shallow equal objects with symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 1 } + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for shallow different values for symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 2 } + expect(shallow(objA, objB)).toBe(false) + }) + test('should return true for shallowly equal maps', () => { const objA = new Map([['1', 'hello']]) const objB = new Map([['1', 'hello']]) diff --git a/packages/vue-store/tests/test.test-d.ts b/packages/vue-store/tests/test.test-d.ts index 5b0108b3..8e36f282 100644 --- a/packages/vue-store/tests/test.test-d.ts +++ b/packages/vue-store/tests/test.test-d.ts @@ -1,16 +1,92 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + useAtom, + useSelector, + useSetValue, + useStore, + useStoreActions, + useValue, +} from '../src' import type { Ref } from 'vue-demi' +import type { Atom } from '@tanstack/store' -test('useStore works with derived state', () => { +test('useSelector works with derived state', () => { const store = createStore(12) const derived = createStore(() => store.state * 2) - const val = useStore(derived, (state) => { + const val = useSelector(derived, (state) => { expectTypeOf(state).toEqualTypeOf() return state }) expectTypeOf(val).toEqualTypeOf>>() }) + +test('useValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toEqualTypeOf>>() + expectTypeOf(useValue(readonlyAtom)).toEqualTypeOf>>() + expectTypeOf(useValue(writableStore)).toEqualTypeOf>>() + expectTypeOf(useValue(readonlyStore)).toEqualTypeOf>>() +}) + +test('useSetValue preserves native setter contracts', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() + expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< + typeof writableStore.setState + >() + // @ts-expect-error readonly atoms cannot be set + useSetValue(readonlyAtom) + // @ts-expect-error readonly stores cannot be set + useSetValue(readonlyStore) +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf>>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useStore matches useSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = useSelector(store, (state) => state) + const compatValue = useStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf>>() + expectTypeOf(compatValue).toEqualTypeOf>>() +}) + +test('useStoreActions infers the action bag from writable stores', () => { + const store = createStore({ count: 0 }, ({ get, set }) => ({ + inc: () => set((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })) + + const actions = useStoreActions(store) + + expectTypeOf(actions.inc).toBeFunction() + expectTypeOf(actions.current()).toExtend() + + const plainStore = createStore(12) + expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() + + const readonlyStore = createStore(() => 24) + // @ts-expect-error readonly stores do not expose actions + useStoreActions(readonlyStore) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e1ba3de..f8c59b6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,55 @@ importers: specifier: ^4.1.4 version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + examples/angular/atoms: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + examples/angular/simple: dependencies: '@angular/animations': @@ -144,6 +193,181 @@ importers: specifier: ^6.0.2 version: 6.0.2 + examples/angular/store-actions: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + + examples/angular/store-context: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + + examples/angular/stores: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + + examples/preact/atoms: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + examples/preact/simple: dependencies: '@tanstack/preact-store': @@ -172,6 +396,90 @@ importers: specifier: ^8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + examples/preact/store-actions: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/preact/store-context: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/preact/stores: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + examples/react/atoms: dependencies: '@tanstack/react-store': @@ -247,76 +555,264 @@ importers: specifier: ^8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - examples/react/store-context: + examples/react/store-context: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/react/stores: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/solid/atoms: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/solid/simple: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/solid/store-actions: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/solid/store-context: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/solid/stores: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/svelte/atoms: + dependencies: + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/svelte/simple: dependencies: - '@tanstack/react-store': - specifier: ^0.10.0 - version: link:../../../packages/react-store - react: - specifier: ^19.2.5 - version: 19.2.5 - react-dom: - specifier: ^19.2.5 - version: 19.2.5(react@19.2.5) + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store devDependencies: - '@types/react': - specifier: ^19.2.14 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 vite: specifier: ^8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - examples/react/stores: + examples/svelte/store-actions: dependencies: - '@tanstack/react-store': - specifier: ^0.10.0 - version: link:../../../packages/react-store - react: - specifier: ^19.2.5 - version: 19.2.5 - react-dom: - specifier: ^19.2.5 - version: 19.2.5(react@19.2.5) + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store devDependencies: - '@types/react': - specifier: ^19.2.14 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 vite: specifier: ^8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - examples/solid/simple: + examples/svelte/store-context: dependencies: - '@tanstack/solid-store': - specifier: ^0.10.0 - version: link:../../../packages/solid-store - solid-js: - specifier: ^1.9.12 - version: 1.9.12 + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 typescript: specifier: 6.0.2 version: 6.0.2 vite: specifier: ^8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-solid: - specifier: ^2.11.12 - version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - examples/svelte/simple: + examples/svelte/stores: dependencies: '@tanstack/svelte-store': specifier: ^0.11.0 @@ -344,6 +840,28 @@ importers: specifier: ^8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + examples/vue/atoms: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + examples/vue/simple: dependencies: '@tanstack/vue-store': @@ -366,6 +884,72 @@ importers: specifier: ^3.2.6 version: 3.2.6(typescript@6.0.2) + examples/vue/store-actions: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + + examples/vue/store-context: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + + examples/vue/stores: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + packages/angular-store: dependencies: '@tanstack/store': @@ -8773,7 +9357,7 @@ snapshots: tree-kill: 1.2.2 tslib: 2.8.1 typescript: 6.0.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) webpack-merge: 6.0.1 @@ -8812,7 +9396,7 @@ snapshots: dependencies: '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) rxjs: 7.8.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) transitivePeerDependencies: - chokidar @@ -8883,7 +9467,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2) postcss: 8.5.6 - vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -10779,7 +11363,7 @@ snapshots: dependencies: '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) typescript: 6.0.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: @@ -12122,7 +12706,7 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -12135,14 +12719,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.1.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 @@ -12606,7 +13182,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 find-up: 5.0.0 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) babel-plugin-jsx-dom-expressions@0.40.6(@babel/core@7.29.0): dependencies: @@ -13004,7 +13580,7 @@ snapshots: schema-utils: 4.3.3 serialize-javascript: 7.0.5 tinyglobby: 0.2.16 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) core-js-compat@3.49.0: dependencies: @@ -13043,7 +13619,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) css-select@5.2.2: dependencies: @@ -14763,7 +15339,7 @@ snapshots: dependencies: less: 4.4.2 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) less@4.4.2: dependencies: @@ -14802,7 +15378,7 @@ snapshots: dependencies: webpack-sources: 3.3.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) lightningcss-android-arm64@1.32.0: optional: true @@ -15073,7 +15649,7 @@ snapshots: dependencies: schema-utils: 4.3.3 tapable: 2.3.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) minimalistic-assert@1.0.1: {} @@ -15726,7 +16302,7 @@ snapshots: postcss: 8.5.6 semver: 7.7.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) transitivePeerDependencies: - typescript @@ -16215,7 +16791,7 @@ snapshots: neo-async: 2.6.2 optionalDependencies: sass: 1.97.3 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) sass@1.97.3: dependencies: @@ -16514,7 +17090,7 @@ snapshots: dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) source-map-support@0.5.21: dependencies: @@ -16760,15 +17336,15 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.4.0(esbuild@0.27.4)(webpack@5.105.2(esbuild@0.27.3)): + terser-webpack-plugin@5.4.0(esbuild@0.27.3)(webpack@5.105.2(esbuild@0.27.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.0 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) optionalDependencies: - esbuild: 0.27.4 + esbuild: 0.27.3 terser@5.46.0: dependencies: @@ -17170,35 +17746,6 @@ snapshots: optionalDependencies: vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vitest@4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.0.4 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.6.0 - '@vitest/coverage-istanbul': 4.1.4(vitest@4.1.4) - jsdom: 29.0.2 - transitivePeerDependencies: - - msw - vitest@4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 @@ -17308,7 +17855,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) transitivePeerDependencies: - tslib @@ -17343,7 +17890,7 @@ snapshots: webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) ws: 8.20.0 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) transitivePeerDependencies: - bufferutil - debug @@ -17362,9 +17909,9 @@ snapshots: webpack-subresource-integrity@5.1.0(webpack@5.105.2(esbuild@0.27.3)): dependencies: typed-assert: 1.0.9 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) - webpack@5.105.2(esbuild@0.27.4): + webpack@5.105.2(esbuild@0.27.3): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -17388,7 +17935,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(esbuild@0.27.4)(webpack@5.105.2(esbuild@0.27.3)) + terser-webpack-plugin: 5.4.0(esbuild@0.27.3)(webpack@5.105.2(esbuild@0.27.3)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: From 1e3a3ab324ffe42480794297c960ae5110aab0f1 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 12 Apr 2026 12:39:56 -0500 Subject: [PATCH 2/5] new changeset --- .changeset/petite-actors-lie2.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/petite-actors-lie2.md diff --git a/.changeset/petite-actors-lie2.md b/.changeset/petite-actors-lie2.md new file mode 100644 index 00000000..df0b90d7 --- /dev/null +++ b/.changeset/petite-actors-lie2.md @@ -0,0 +1,10 @@ +--- +'@tanstack/angular-store': minor +'@tanstack/preact-store': minor +'@tanstack/svelte-store': minor +'@tanstack/solid-store': minor +'@tanstack/vue-store': minor +'@tanstack/store': minor +--- + +feat: introduce more frameworks hooks for other non-react adapters From f4d675b5d970afe0398be3ce9598dce16522547d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:40:57 +0000 Subject: [PATCH 3/5] ci: apply automated fixes and generate docs --- .../preact/reference/functions/createStoreContext.md | 2 +- docs/framework/preact/reference/functions/shallow.md | 2 +- docs/framework/preact/reference/functions/useAtom.md | 2 +- docs/framework/preact/reference/functions/useCreateAtom.md | 4 ++-- docs/framework/preact/reference/functions/useCreateStore.md | 6 +++--- docs/framework/preact/reference/functions/useSelector.md | 2 +- docs/framework/preact/reference/functions/useSetValue.md | 4 ++-- docs/framework/preact/reference/functions/useStore.md | 2 +- .../framework/preact/reference/functions/useStoreActions.md | 2 +- docs/framework/preact/reference/functions/useValue.md | 2 +- .../preact/reference/interfaces/UseSelectorOptions.md | 4 ++-- docs/framework/solid/reference/functions/shallow.md | 2 +- docs/framework/solid/reference/functions/useAtom.md | 2 +- docs/framework/solid/reference/functions/useSelector.md | 2 +- docs/framework/solid/reference/functions/useSetValue.md | 4 ++-- docs/framework/solid/reference/functions/useStore.md | 2 +- docs/framework/solid/reference/functions/useStoreActions.md | 2 +- docs/framework/solid/reference/functions/useValue.md | 2 +- .../solid/reference/interfaces/UseSelectorOptions.md | 4 ++-- docs/framework/vue/reference/functions/shallow.md | 2 +- docs/framework/vue/reference/functions/useAtom.md | 2 +- docs/framework/vue/reference/functions/useSelector.md | 2 +- docs/framework/vue/reference/functions/useSetValue.md | 4 ++-- docs/framework/vue/reference/functions/useStore.md | 2 +- docs/framework/vue/reference/functions/useStoreActions.md | 2 +- docs/framework/vue/reference/functions/useValue.md | 2 +- .../vue/reference/interfaces/UseSelectorOptions.md | 4 ++-- 27 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/framework/preact/reference/functions/createStoreContext.md b/docs/framework/preact/reference/functions/createStoreContext.md index e6e368f9..096e5493 100644 --- a/docs/framework/preact/reference/functions/createStoreContext.md +++ b/docs/framework/preact/reference/functions/createStoreContext.md @@ -9,7 +9,7 @@ title: createStoreContext function createStoreContext(): object; ``` -Defined in: preact-store/src/createStoreContext.tsx:44 +Defined in: [preact-store/src/createStoreContext.tsx:44](https://github.com/TanStack/store/blob/main/packages/preact-store/src/createStoreContext.tsx#L44) Creates a typed Preact context for sharing a bundle of atoms and stores with a subtree. diff --git a/docs/framework/preact/reference/functions/shallow.md b/docs/framework/preact/reference/functions/shallow.md index 007453d0..d4fcbf03 100644 --- a/docs/framework/preact/reference/functions/shallow.md +++ b/docs/framework/preact/reference/functions/shallow.md @@ -9,7 +9,7 @@ title: shallow function shallow(objA, objB): boolean; ``` -Defined in: preact-store/src/shallow.ts:1 +Defined in: [preact-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/preact-store/src/shallow.ts#L1) ## Type Parameters diff --git a/docs/framework/preact/reference/functions/useAtom.md b/docs/framework/preact/reference/functions/useAtom.md index 824f1323..fbf348c6 100644 --- a/docs/framework/preact/reference/functions/useAtom.md +++ b/docs/framework/preact/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [TValue, (fn) => void & (value) => void]; ``` -Defined in: preact-store/src/useAtom.ts:23 +Defined in: [preact-store/src/useAtom.ts:23](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useAtom.ts#L23) Returns the current atom value together with a stable setter. diff --git a/docs/framework/preact/reference/functions/useCreateAtom.md b/docs/framework/preact/reference/functions/useCreateAtom.md index 527b6e2c..a92253d0 100644 --- a/docs/framework/preact/reference/functions/useCreateAtom.md +++ b/docs/framework/preact/reference/functions/useCreateAtom.md @@ -11,7 +11,7 @@ title: useCreateAtom function useCreateAtom(getValue, options?): ReadonlyAtom; ``` -Defined in: preact-store/src/useCreateAtom.ts:27 +Defined in: [preact-store/src/useCreateAtom.ts:27](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateAtom.ts#L27) Creates a stable atom instance for the lifetime of the component. @@ -60,7 +60,7 @@ function Counter() { function useCreateAtom(initialValue, options?): Atom; ``` -Defined in: preact-store/src/useCreateAtom.ts:31 +Defined in: [preact-store/src/useCreateAtom.ts:31](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateAtom.ts#L31) Creates a stable atom instance for the lifetime of the component. diff --git a/docs/framework/preact/reference/functions/useCreateStore.md b/docs/framework/preact/reference/functions/useCreateStore.md index 79176987..56b8f91c 100644 --- a/docs/framework/preact/reference/functions/useCreateStore.md +++ b/docs/framework/preact/reference/functions/useCreateStore.md @@ -11,7 +11,7 @@ title: useCreateStore function useCreateStore(getValue): ReadonlyStore; ``` -Defined in: preact-store/src/useCreateStore.ts:38 +Defined in: [preact-store/src/useCreateStore.ts:38](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateStore.ts#L38) Creates a stable store instance for the lifetime of the component. @@ -60,7 +60,7 @@ function Counter() { function useCreateStore(initialValue): Store; ``` -Defined in: preact-store/src/useCreateStore.ts:41 +Defined in: [preact-store/src/useCreateStore.ts:41](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateStore.ts#L41) Creates a stable store instance for the lifetime of the component. @@ -109,7 +109,7 @@ function Counter() { function useCreateStore(initialValue, actions): Store; ``` -Defined in: preact-store/src/useCreateStore.ts:42 +Defined in: [preact-store/src/useCreateStore.ts:42](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateStore.ts#L42) Creates a stable store instance for the lifetime of the component. diff --git a/docs/framework/preact/reference/functions/useSelector.md b/docs/framework/preact/reference/functions/useSelector.md index 8a9c4343..7a406e6b 100644 --- a/docs/framework/preact/reference/functions/useSelector.md +++ b/docs/framework/preact/reference/functions/useSelector.md @@ -12,7 +12,7 @@ function useSelector( options?): TSelected; ``` -Defined in: preact-store/src/useSelector.ts:127 +Defined in: [preact-store/src/useSelector.ts:127](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSelector.ts#L127) Selects a slice of state from an atom or store and subscribes the component to that selection. diff --git a/docs/framework/preact/reference/functions/useSetValue.md b/docs/framework/preact/reference/functions/useSetValue.md index f66e138c..e8a6e1b0 100644 --- a/docs/framework/preact/reference/functions/useSetValue.md +++ b/docs/framework/preact/reference/functions/useSetValue.md @@ -11,7 +11,7 @@ title: useSetValue function useSetValue(source): (fn) => void & (value) => void; ``` -Defined in: preact-store/src/useSetValue.ts:22 +Defined in: [preact-store/src/useSetValue.ts:22](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSetValue.ts#L22) Returns a stable setter for a writable atom or store. @@ -52,7 +52,7 @@ setState((state) => ({ ...state, count: state.count + 1 })) function useSetValue(source): (updater) => void; ``` -Defined in: preact-store/src/useSetValue.ts:23 +Defined in: [preact-store/src/useSetValue.ts:23](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSetValue.ts#L23) Returns a stable setter for a writable atom or store. diff --git a/docs/framework/preact/reference/functions/useStore.md b/docs/framework/preact/reference/functions/useStore.md index a8bd22a4..24850d6d 100644 --- a/docs/framework/preact/reference/functions/useStore.md +++ b/docs/framework/preact/reference/functions/useStore.md @@ -12,7 +12,7 @@ function useStore( compare?): TSelected; ``` -Defined in: preact-store/src/useStore.ts:13 +Defined in: [preact-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useStore.ts#L13) Deprecated alias for [useSelector](useSelector.md). diff --git a/docs/framework/preact/reference/functions/useStoreActions.md b/docs/framework/preact/reference/functions/useStoreActions.md index 32f8e50d..a2f20217 100644 --- a/docs/framework/preact/reference/functions/useStoreActions.md +++ b/docs/framework/preact/reference/functions/useStoreActions.md @@ -9,7 +9,7 @@ title: useStoreActions function useStoreActions(store): TActions; ``` -Defined in: preact-store/src/useStoreActions.ts:16 +Defined in: [preact-store/src/useStoreActions.ts:16](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useStoreActions.ts#L16) Returns the stable actions bag from a writable store created with actions. diff --git a/docs/framework/preact/reference/functions/useValue.md b/docs/framework/preact/reference/functions/useValue.md index 7c1e8224..78d43934 100644 --- a/docs/framework/preact/reference/functions/useValue.md +++ b/docs/framework/preact/reference/functions/useValue.md @@ -9,7 +9,7 @@ title: useValue function useValue(source, options?): TValue; ``` -Defined in: preact-store/src/useValue.ts:22 +Defined in: [preact-store/src/useValue.ts:22](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useValue.ts#L22) Subscribes to an atom or store and returns its current value. diff --git a/docs/framework/preact/reference/interfaces/UseSelectorOptions.md b/docs/framework/preact/reference/interfaces/UseSelectorOptions.md index f70163cf..b3a6fa5e 100644 --- a/docs/framework/preact/reference/interfaces/UseSelectorOptions.md +++ b/docs/framework/preact/reference/interfaces/UseSelectorOptions.md @@ -5,7 +5,7 @@ title: UseSelectorOptions # Interface: UseSelectorOptions\ -Defined in: preact-store/src/useSelector.ts:9 +Defined in: [preact-store/src/useSelector.ts:9](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSelector.ts#L9) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: preact-store/src/useSelector.ts:9 optional compare: (a, b) => boolean; ``` -Defined in: preact-store/src/useSelector.ts:10 +Defined in: [preact-store/src/useSelector.ts:10](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSelector.ts#L10) #### Parameters diff --git a/docs/framework/solid/reference/functions/shallow.md b/docs/framework/solid/reference/functions/shallow.md index eebc35dc..1b63b6c3 100644 --- a/docs/framework/solid/reference/functions/shallow.md +++ b/docs/framework/solid/reference/functions/shallow.md @@ -9,7 +9,7 @@ title: shallow function shallow(objA, objB): boolean; ``` -Defined in: solid-store/src/shallow.ts:1 +Defined in: [solid-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/solid-store/src/shallow.ts#L1) ## Type Parameters diff --git a/docs/framework/solid/reference/functions/useAtom.md b/docs/framework/solid/reference/functions/useAtom.md index ffc97fee..e73a7b3b 100644 --- a/docs/framework/solid/reference/functions/useAtom.md +++ b/docs/framework/solid/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [Accessor, (fn) => void & (value) => void]; ``` -Defined in: solid-store/src/useAtom.ts:24 +Defined in: [solid-store/src/useAtom.ts:24](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useAtom.ts#L24) Returns the current atom accessor together with a setter. diff --git a/docs/framework/solid/reference/functions/useSelector.md b/docs/framework/solid/reference/functions/useSelector.md index fecd9f0f..f2a109fd 100644 --- a/docs/framework/solid/reference/functions/useSelector.md +++ b/docs/framework/solid/reference/functions/useSelector.md @@ -12,7 +12,7 @@ function useSelector( options?): Accessor; ``` -Defined in: solid-store/src/useSelector.ts:38 +Defined in: [solid-store/src/useSelector.ts:38](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSelector.ts#L38) Selects a slice of state from an atom or store and subscribes the component to that selection. diff --git a/docs/framework/solid/reference/functions/useSetValue.md b/docs/framework/solid/reference/functions/useSetValue.md index 7c206203..88ab5cbf 100644 --- a/docs/framework/solid/reference/functions/useSetValue.md +++ b/docs/framework/solid/reference/functions/useSetValue.md @@ -11,7 +11,7 @@ title: useSetValue function useSetValue(source): (fn) => void & (value) => void; ``` -Defined in: solid-store/src/useSetValue.ts:21 +Defined in: [solid-store/src/useSetValue.ts:21](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSetValue.ts#L21) Returns a stable setter for a writable atom or store. @@ -52,7 +52,7 @@ setState((state) => ({ ...state, count: state.count + 1 })) function useSetValue(source): (updater) => void; ``` -Defined in: solid-store/src/useSetValue.ts:22 +Defined in: [solid-store/src/useSetValue.ts:22](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSetValue.ts#L22) Returns a stable setter for a writable atom or store. diff --git a/docs/framework/solid/reference/functions/useStore.md b/docs/framework/solid/reference/functions/useStore.md index 403da190..6e73b2fc 100644 --- a/docs/framework/solid/reference/functions/useStore.md +++ b/docs/framework/solid/reference/functions/useStore.md @@ -12,7 +12,7 @@ function useStore( compare?): Accessor; ``` -Defined in: solid-store/src/useStore.ts:14 +Defined in: [solid-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useStore.ts#L14) Deprecated alias for [useSelector](useSelector.md). diff --git a/docs/framework/solid/reference/functions/useStoreActions.md b/docs/framework/solid/reference/functions/useStoreActions.md index 64a7c1b8..976e4a4f 100644 --- a/docs/framework/solid/reference/functions/useStoreActions.md +++ b/docs/framework/solid/reference/functions/useStoreActions.md @@ -9,7 +9,7 @@ title: useStoreActions function useStoreActions(store): TActions; ``` -Defined in: solid-store/src/useStoreActions.ts:15 +Defined in: [solid-store/src/useStoreActions.ts:15](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useStoreActions.ts#L15) Returns the stable actions bag from a writable store created with actions. diff --git a/docs/framework/solid/reference/functions/useValue.md b/docs/framework/solid/reference/functions/useValue.md index 3eec9903..770bda4d 100644 --- a/docs/framework/solid/reference/functions/useValue.md +++ b/docs/framework/solid/reference/functions/useValue.md @@ -9,7 +9,7 @@ title: useValue function useValue(source, options?): Accessor; ``` -Defined in: solid-store/src/useValue.ts:24 +Defined in: [solid-store/src/useValue.ts:24](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useValue.ts#L24) Subscribes to an atom or store and returns its current value accessor. diff --git a/docs/framework/solid/reference/interfaces/UseSelectorOptions.md b/docs/framework/solid/reference/interfaces/UseSelectorOptions.md index f5f2ec6a..2d3845b8 100644 --- a/docs/framework/solid/reference/interfaces/UseSelectorOptions.md +++ b/docs/framework/solid/reference/interfaces/UseSelectorOptions.md @@ -5,7 +5,7 @@ title: UseSelectorOptions # Interface: UseSelectorOptions\ -Defined in: solid-store/src/useSelector.ts:4 +Defined in: [solid-store/src/useSelector.ts:4](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSelector.ts#L4) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: solid-store/src/useSelector.ts:4 optional compare: (a, b) => boolean; ``` -Defined in: solid-store/src/useSelector.ts:5 +Defined in: [solid-store/src/useSelector.ts:5](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSelector.ts#L5) #### Parameters diff --git a/docs/framework/vue/reference/functions/shallow.md b/docs/framework/vue/reference/functions/shallow.md index cc5dd1ef..10e38b27 100644 --- a/docs/framework/vue/reference/functions/shallow.md +++ b/docs/framework/vue/reference/functions/shallow.md @@ -9,7 +9,7 @@ title: shallow function shallow(objA, objB): boolean; ``` -Defined in: vue-store/src/shallow.ts:1 +Defined in: [vue-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/vue-store/src/shallow.ts#L1) ## Type Parameters diff --git a/docs/framework/vue/reference/functions/useAtom.md b/docs/framework/vue/reference/functions/useAtom.md index d46a8f0a..487eff7e 100644 --- a/docs/framework/vue/reference/functions/useAtom.md +++ b/docs/framework/vue/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [Readonly>, (fn) => void & (value) => void]; ``` -Defined in: vue-store/src/useAtom.ts:21 +Defined in: [vue-store/src/useAtom.ts:21](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useAtom.ts#L21) Returns the current atom ref together with a setter. diff --git a/docs/framework/vue/reference/functions/useSelector.md b/docs/framework/vue/reference/functions/useSelector.md index 3a771cfc..ee43477e 100644 --- a/docs/framework/vue/reference/functions/useSelector.md +++ b/docs/framework/vue/reference/functions/useSelector.md @@ -12,7 +12,7 @@ function useSelector( options?): Readonly>; ``` -Defined in: vue-store/src/useSelector.ts:37 +Defined in: [vue-store/src/useSelector.ts:37](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSelector.ts#L37) Selects a slice of state from an atom or store and subscribes the component to that selection. diff --git a/docs/framework/vue/reference/functions/useSetValue.md b/docs/framework/vue/reference/functions/useSetValue.md index 844e5d4a..0be3c06b 100644 --- a/docs/framework/vue/reference/functions/useSetValue.md +++ b/docs/framework/vue/reference/functions/useSetValue.md @@ -11,7 +11,7 @@ title: useSetValue function useSetValue(source): (fn) => void & (value) => void; ``` -Defined in: vue-store/src/useSetValue.ts:21 +Defined in: [vue-store/src/useSetValue.ts:21](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSetValue.ts#L21) Returns a stable setter for a writable atom or store. @@ -52,7 +52,7 @@ setState((state) => ({ ...state, count: state.count + 1 })) function useSetValue(source): (updater) => void; ``` -Defined in: vue-store/src/useSetValue.ts:22 +Defined in: [vue-store/src/useSetValue.ts:22](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSetValue.ts#L22) Returns a stable setter for a writable atom or store. diff --git a/docs/framework/vue/reference/functions/useStore.md b/docs/framework/vue/reference/functions/useStore.md index 788bdff8..ab199948 100644 --- a/docs/framework/vue/reference/functions/useStore.md +++ b/docs/framework/vue/reference/functions/useStore.md @@ -12,7 +12,7 @@ function useStore( compare?): Readonly>; ``` -Defined in: vue-store/src/useStore.ts:14 +Defined in: [vue-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useStore.ts#L14) Deprecated alias for [useSelector](useSelector.md). diff --git a/docs/framework/vue/reference/functions/useStoreActions.md b/docs/framework/vue/reference/functions/useStoreActions.md index fbd56ad3..122a9f8e 100644 --- a/docs/framework/vue/reference/functions/useStoreActions.md +++ b/docs/framework/vue/reference/functions/useStoreActions.md @@ -9,7 +9,7 @@ title: useStoreActions function useStoreActions(store): TActions; ``` -Defined in: vue-store/src/useStoreActions.ts:15 +Defined in: [vue-store/src/useStoreActions.ts:15](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useStoreActions.ts#L15) Returns the stable actions bag from a writable store created with actions. diff --git a/docs/framework/vue/reference/functions/useValue.md b/docs/framework/vue/reference/functions/useValue.md index 4f82711a..efa4a91e 100644 --- a/docs/framework/vue/reference/functions/useValue.md +++ b/docs/framework/vue/reference/functions/useValue.md @@ -9,7 +9,7 @@ title: useValue function useValue(source, options?): Readonly>; ``` -Defined in: vue-store/src/useValue.ts:23 +Defined in: [vue-store/src/useValue.ts:23](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useValue.ts#L23) Subscribes to an atom or store and returns its current value ref. diff --git a/docs/framework/vue/reference/interfaces/UseSelectorOptions.md b/docs/framework/vue/reference/interfaces/UseSelectorOptions.md index 0e775976..dc0616ce 100644 --- a/docs/framework/vue/reference/interfaces/UseSelectorOptions.md +++ b/docs/framework/vue/reference/interfaces/UseSelectorOptions.md @@ -5,7 +5,7 @@ title: UseSelectorOptions # Interface: UseSelectorOptions\ -Defined in: vue-store/src/useSelector.ts:4 +Defined in: [vue-store/src/useSelector.ts:4](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSelector.ts#L4) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: vue-store/src/useSelector.ts:4 optional compare: (a, b) => boolean; ``` -Defined in: vue-store/src/useSelector.ts:5 +Defined in: [vue-store/src/useSelector.ts:5](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSelector.ts#L5) #### Parameters From 77cbace45fda4549a97e7a2b6d087412e77aa0fe Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 14 Apr 2026 13:06:36 -0500 Subject: [PATCH 4/5] remove dedicated setValue and useAction hooks. Add new useStore hooks with _ --- .../angular/atoms/src/app/app.component.ts | 28 +- examples/angular/store-actions/README.md | 2 +- .../store-actions/src/app/app.component.ts | 48 ++- .../store-context/src/app/app.component.ts | 68 ++-- examples/preact/atoms/src/index.tsx | 11 +- examples/preact/store-actions/README.md | 2 +- examples/preact/store-actions/src/index.tsx | 55 +-- examples/preact/store-context/src/index.tsx | 11 +- examples/react/atoms/src/index.tsx | 11 +- examples/react/store-actions/README.md | 2 +- examples/react/store-actions/src/index.tsx | 53 +-- examples/react/store-context/src/index.tsx | 11 +- examples/solid/atoms/src/index.tsx | 17 +- examples/solid/store-actions/README.md | 2 +- examples/solid/store-actions/src/index.tsx | 55 +-- examples/solid/store-context/src/index.tsx | 11 +- examples/svelte/atoms/src/App.svelte | 14 +- examples/svelte/store-actions/README.md | 2 +- examples/svelte/store-actions/src/App.svelte | 34 +- .../store-context/src/AtomSection.svelte | 9 +- .../store-context/src/StoreSection.svelte | 7 +- examples/vue/atoms/src/App.vue | 9 +- examples/vue/store-actions/README.md | 2 +- examples/vue/store-actions/src/App.vue | 34 +- examples/vue/store-context/src/App.vue | 18 +- packages/angular-store/src/_injectStore.ts | 41 +++ .../angular-store/src/createStoreContext.ts | 71 ++++ packages/angular-store/src/index.ts | 316 +----------------- packages/angular-store/src/injectAtom.ts | 52 +++ packages/angular-store/src/injectSelector.ts | 100 ++++++ packages/angular-store/src/injectStore.ts | 38 +++ packages/angular-store/src/injectValue.ts | 30 ++ packages/angular-store/tests/index.test.ts | 257 +++++++++++--- packages/angular-store/tests/test.test-d.ts | 71 ++-- packages/preact-store/src/_useStore.ts | 43 +++ packages/preact-store/src/index.ts | 7 +- packages/preact-store/src/useAtom.ts | 4 +- packages/preact-store/src/useSetValue.ts | 41 --- packages/preact-store/src/useStoreActions.ts | 20 -- packages/preact-store/tests/index.test.tsx | 124 ++++--- packages/preact-store/tests/test.test-d.ts | 70 ++-- packages/react-store/src/_useStore.ts | 43 +++ packages/react-store/src/index.ts | 7 +- packages/react-store/src/shallow.ts | 57 ---- packages/react-store/src/useAtom.ts | 4 +- packages/react-store/src/useSetValue.ts | 44 --- packages/react-store/src/useStoreActions.ts | 20 -- packages/react-store/tests/index.test.tsx | 124 ++++--- packages/react-store/tests/test.test-d.ts | 70 ++-- packages/solid-store/src/_useStore.ts | 40 +++ packages/solid-store/src/index.tsx | 7 +- packages/solid-store/src/shallow.ts | 57 ---- packages/solid-store/src/useAtom.ts | 4 +- packages/solid-store/src/useSetValue.ts | 39 --- packages/solid-store/src/useStoreActions.ts | 19 -- packages/solid-store/tests/index.test.tsx | 66 ++-- packages/solid-store/tests/test.test-d.ts | 50 +-- packages/store/src/index.ts | 1 + .../{preact-store => store}/src/shallow.ts | 0 packages/store/src/store.ts | 20 +- .../store/tests/store-type-safety.test.ts | 14 +- packages/store/tests/store.test.ts | 12 +- packages/svelte-store/src/_useStore.ts | 38 +++ packages/svelte-store/src/index.svelte.ts | 244 +------------- packages/svelte-store/src/useAtom.ts | 25 ++ .../svelte-store/src/useSelector.svelte.ts | 57 ++++ packages/svelte-store/src/useStore.ts | 23 ++ packages/svelte-store/src/useValue.ts | 29 ++ packages/svelte-store/tests/index.test.ts | 14 +- packages/svelte-store/tests/test.test-d.ts | 47 +-- packages/vue-store/src/_useStore.ts | 41 +++ packages/vue-store/src/index.ts | 7 +- packages/vue-store/src/shallow.ts | 57 ---- packages/vue-store/src/useAtom.ts | 4 +- packages/vue-store/src/useSetValue.ts | 39 --- packages/vue-store/src/useStoreActions.ts | 19 -- packages/vue-store/tests/index.test.tsx | 89 ++--- packages/vue-store/tests/test.test-d.ts | 50 +-- 78 files changed, 1501 insertions(+), 1781 deletions(-) create mode 100644 packages/angular-store/src/_injectStore.ts create mode 100644 packages/angular-store/src/createStoreContext.ts create mode 100644 packages/angular-store/src/injectAtom.ts create mode 100644 packages/angular-store/src/injectSelector.ts create mode 100644 packages/angular-store/src/injectStore.ts create mode 100644 packages/angular-store/src/injectValue.ts create mode 100644 packages/preact-store/src/_useStore.ts delete mode 100644 packages/preact-store/src/useSetValue.ts delete mode 100644 packages/preact-store/src/useStoreActions.ts create mode 100644 packages/react-store/src/_useStore.ts delete mode 100644 packages/react-store/src/shallow.ts delete mode 100644 packages/react-store/src/useSetValue.ts delete mode 100644 packages/react-store/src/useStoreActions.ts create mode 100644 packages/solid-store/src/_useStore.ts delete mode 100644 packages/solid-store/src/shallow.ts delete mode 100644 packages/solid-store/src/useSetValue.ts delete mode 100644 packages/solid-store/src/useStoreActions.ts rename packages/{preact-store => store}/src/shallow.ts (100%) create mode 100644 packages/svelte-store/src/_useStore.ts create mode 100644 packages/svelte-store/src/useAtom.ts create mode 100644 packages/svelte-store/src/useSelector.svelte.ts create mode 100644 packages/svelte-store/src/useStore.ts create mode 100644 packages/svelte-store/src/useValue.ts create mode 100644 packages/vue-store/src/_useStore.ts delete mode 100644 packages/vue-store/src/shallow.ts delete mode 100644 packages/vue-store/src/useSetValue.ts delete mode 100644 packages/vue-store/src/useStoreActions.ts diff --git a/examples/angular/atoms/src/app/app.component.ts b/examples/angular/atoms/src/app/app.component.ts index feb884d6..71c45e54 100644 --- a/examples/angular/atoms/src/app/app.component.ts +++ b/examples/angular/atoms/src/app/app.component.ts @@ -1,10 +1,5 @@ import { Component } from '@angular/core' -import { - createAtom, - injectAtom, - injectSetValue, - injectValue, -} from '@tanstack/angular-store' +import { createAtom, injectAtom } from '@tanstack/angular-store' // Optionally, you can create atoms outside of Angular components at module scope const countAtom = createAtom(0) @@ -19,28 +14,21 @@ const countAtom = createAtom(0) This example creates a module-level atom and reads and updates it with the Angular hooks.

-

Total: {{ count() }}

+

Count: {{ count() }}

- - +
-

Editable count: {{ editableCount() }}

-
`, }) export class AppComponent { - count = injectValue(countAtom) - setCount = injectSetValue(countAtom) - private readonly editableAtom = injectAtom(countAtom) - editableCount = this.editableAtom[0] - setEditableCount = this.editableAtom[1] + count = injectAtom(countAtom) } diff --git a/examples/angular/store-actions/README.md b/examples/angular/store-actions/README.md index d4bbfb7d..1e05fc10 100644 --- a/examples/angular/store-actions/README.md +++ b/examples/angular/store-actions/README.md @@ -3,7 +3,7 @@ This example demonstrates: - `injectSelector` -- `injectStoreActions` +- `_injectStore` - module-level `Store` actions To run this example: diff --git a/examples/angular/store-actions/src/app/app.component.ts b/examples/angular/store-actions/src/app/app.component.ts index 9179316a..97c5e9e1 100644 --- a/examples/angular/store-actions/src/app/app.component.ts +++ b/examples/angular/store-actions/src/app/app.component.ts @@ -1,9 +1,5 @@ import { Component } from '@angular/core' -import { - injectSelector, - injectStoreActions, - Store, -} from '@tanstack/angular-store' +import { _injectStore, injectSelector, Store } from '@tanstack/angular-store' // Optionally, you can create stores outside of Angular components at module scope const petStore = new Store( @@ -11,19 +7,20 @@ const petStore = new Store( cats: 0, dogs: 0, }, - ({ set }) => + ({ setState, get }) => // optionally, define actions for updating your store in specific ways right on the store. ({ addCat: () => - set((prev) => ({ + setState((prev) => ({ ...prev, cats: prev.cats + 1, })), addDog: () => - set((prev) => ({ + setState((prev) => ({ ...prev, dogs: prev.dogs + 1, })), + log: () => console.log(get()), }), ) @@ -32,25 +29,42 @@ const petStore = new Store( standalone: true, template: `
+

Angular Store Actions

This example creates a module-level store with actions. Components read state with \`injectSelector\` and call mutations through - \`injectStoreActions\`. + \`store.actions\` or the experimental \`_injectStore\` hook.

-

Cats: {{ cats() }}

-

Dogs: {{ dogs() }}

-

Total votes: {{ total() }}

- - +

Cats: {{ cats() }}

+ +
+
+

Dogs: {{ dogs() }}

+
+

Total votes: {{ total() }}

`, }) export class AppComponent { - cats = injectSelector(petStore, (state) => state.cats) - dogs = injectSelector(petStore, (state) => state.dogs) + // _injectStore gives both the selected signal and actions in a single tuple + private catResult = _injectStore(petStore, (state) => state.cats) + cats = this.catResult[0] + catActions = this.catResult[1] + + private dogResult = _injectStore(petStore, (state) => state.dogs) + dogs = this.dogResult[0] + dogActions = this.dogResult[1] + total = injectSelector(petStore, (state) => state.cats + state.dogs) - actions = injectStoreActions(petStore) + + log() { + petStore.actions.log() + } } diff --git a/examples/angular/store-context/src/app/app.component.ts b/examples/angular/store-context/src/app/app.component.ts index 2d99a866..fe1b19ba 100644 --- a/examples/angular/store-context/src/app/app.component.ts +++ b/examples/angular/store-context/src/app/app.component.ts @@ -1,47 +1,43 @@ -import { Component, InjectionToken, inject } from '@angular/core' +import { Component } from '@angular/core' import { createAtom, + createStoreContext, injectAtom, injectSelector, - injectSetValue, injectValue, Store, } from '@tanstack/angular-store' import type { Atom } from '@tanstack/angular-store' -// one drawback of storing stores and atoms in context is you have to define types for the context manually, instead of everything being inferred. - type CounterStore = { cats: number dogs: number } -const VOTES_STORE = new InjectionToken>('votes-store') -const COUNT_ATOM = new InjectionToken>('count-atom') +const { provideStoreContext, injectStoreContext } = createStoreContext<{ + votesStore: Store + countAtom: Atom +}>() @Component({ selector: 'app-root', standalone: true, providers: [ - { - provide: VOTES_STORE, - useFactory: () => - new Store({ - cats: 0, - dogs: 0, - }), - }, - { - provide: COUNT_ATOM, - useFactory: () => createAtom(0), - }, + provideStoreContext(() => ({ + votesStore: new Store({ + cats: 0, + dogs: 0, + }), + countAtom: createAtom(0), + })), ], template: `

Angular Store Context

- This example provides both atoms and stores through Angular DI, then - consumes them from the same component tree with TanStack Store hooks. + This example provides both atoms and stores through Angular DI via + \`createStoreContext\`, then consumes them from the same component tree + with TanStack Store hooks.

Cats: {{ cats() }}

Dogs: {{ dogs() }}

@@ -54,43 +50,35 @@ const COUNT_ATOM = new InjectionToken>('count-atom')

Nested Atom Components

Atom count: {{ count() }}

- - -
-
-

Editable atom count: {{ editableCount() }}

- +
`, }) export class AppComponent { - votesStore = inject(VOTES_STORE) - countAtom = inject(COUNT_ATOM) - private readonly editableAtom = injectAtom(this.countAtom) + private ctx = injectStoreContext() - cats = injectSelector(this.votesStore, (state) => state.cats) - dogs = injectSelector(this.votesStore, (state) => state.dogs) - total = injectSelector(this.votesStore, (state) => state.cats + state.dogs) - count = injectValue(this.countAtom) - setCount = injectSetValue(this.countAtom) - editableCount = this.editableAtom[0] - setEditableCount = this.editableAtom[1] + cats = injectSelector(this.ctx.votesStore, (state) => state.cats) + dogs = injectSelector(this.ctx.votesStore, (state) => state.dogs) + total = injectSelector( + this.ctx.votesStore, + (state) => state.cats + state.dogs, + ) + count = injectAtom(this.ctx.countAtom) addCat() { - this.votesStore.setState((prev) => ({ + this.ctx.votesStore.setState((prev) => ({ ...prev, cats: prev.cats + 1, })) } addDog() { - this.votesStore.setState((prev) => ({ + this.ctx.votesStore.setState((prev) => ({ ...prev, dogs: prev.dogs + 1, })) diff --git a/examples/preact/atoms/src/index.tsx b/examples/preact/atoms/src/index.tsx index 4563f64a..bd29194d 100644 --- a/examples/preact/atoms/src/index.tsx +++ b/examples/preact/atoms/src/index.tsx @@ -3,7 +3,6 @@ import { createAtom, useAtom, // useCreateAtom, - useSetValue, useValue, } from '@tanstack/preact-store' @@ -35,15 +34,13 @@ function AtomValuePanel() { } function AtomButtons() { - const setCount = useSetValue(countAtom) // useSetValue never causes a re-render (useAtom does) if you need write-only in a component - return (
- -
) diff --git a/examples/preact/store-actions/README.md b/examples/preact/store-actions/README.md index 8b5588fd..d019ddb4 100644 --- a/examples/preact/store-actions/README.md +++ b/examples/preact/store-actions/README.md @@ -3,7 +3,7 @@ This example demonstrates: - `useSelector` -- `useStoreActions` +- `_useStore` - module-level `Store` actions To run this example: diff --git a/examples/preact/store-actions/src/index.tsx b/examples/preact/store-actions/src/index.tsx index 3cd0f56b..96924921 100644 --- a/examples/preact/store-actions/src/index.tsx +++ b/examples/preact/store-actions/src/index.tsx @@ -1,5 +1,5 @@ import { render } from 'preact' -import { Store, useSelector, useStoreActions } from '@tanstack/preact-store' +import { Store, _useStore, useSelector } from '@tanstack/preact-store' // Optionally, you can create stores outside of Preact components at module scope const petStore = new Store( @@ -7,19 +7,20 @@ const petStore = new Store( cats: 0, dogs: 0, }, - ({ set }) => + ({ setState, get }) => // optionally, define actions for updating your store in specific ways right on the store. ({ addCat: () => - set((prev) => ({ + setState((prev) => ({ ...prev, cats: prev.cats + 1, })), addDog: () => - set((prev) => ({ + setState((prev) => ({ ...prev, dogs: prev.dogs + 1, })), + log: () => console.log(get()), }), ) @@ -29,44 +30,46 @@ function App() { return (
+

Preact Store Actions

This example creates a module-level store with actions. Components read - state with `useSelector` and call mutations through `useStoreActions`. + state with useSelector and call mutations through{' '} + store.actions or the experimental _useStore{' '} + hook.

- - + + -
) } -function CatsCard() { - // read state slice (only re-renders when the selected value changes) - const value = useSelector(petStore, (state) => state.cats) - - return

Cats: {value}

-} - -function DogsCard() { - // read state slice (only re-renders when the selected value changes) - const value = useSelector(petStore, (state) => state.dogs) +function CatVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [cats, { addCat }] = _useStore(petStore, (state) => state.cats) - return

Dogs: {value}

+ return ( +
+

Cats: {cats}

+ +
+ ) } -function StoreButtons() { - // pull stable action functions from the store - const { addCat, addDog } = useStoreActions(petStore) +function DogVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [dogs, { addDog }] = _useStore(petStore, (state) => state.dogs) return (
- +

Dogs: {dogs}

) diff --git a/examples/preact/store-context/src/index.tsx b/examples/preact/store-context/src/index.tsx index de148d0b..44f994c5 100644 --- a/examples/preact/store-context/src/index.tsx +++ b/examples/preact/store-context/src/index.tsx @@ -5,7 +5,6 @@ import { createStoreContext, useCreateStore, useSelector, - useSetValue, useValue, } from '@tanstack/preact-store' import type { Atom, Store } from '@tanstack/preact-store' @@ -97,14 +96,13 @@ function AtomSummary() { function NestedAtomControls() { const { countAtom } = useStoreContext() - const setCount = useSetValue(countAtom) return (
- -
@@ -127,14 +125,13 @@ function DeepAtomEditor() { function StoreButtons() { const { votesStore } = useStoreContext() - const setVotes = useSetValue(votesStore) return (
-
) diff --git a/examples/react/store-actions/README.md b/examples/react/store-actions/README.md index 957f75ee..b36726b9 100644 --- a/examples/react/store-actions/README.md +++ b/examples/react/store-actions/README.md @@ -3,7 +3,7 @@ This example demonstrates: - `useSelector` -- `useStoreActions` +- `_useStore` - module-level `Store` actions To run this example: diff --git a/examples/react/store-actions/src/index.tsx b/examples/react/store-actions/src/index.tsx index 6d7d5161..10717d1f 100644 --- a/examples/react/store-actions/src/index.tsx +++ b/examples/react/store-actions/src/index.tsx @@ -1,5 +1,5 @@ import ReactDOM from 'react-dom/client' -import { Store, useSelector, useStoreActions } from '@tanstack/react-store' +import { Store, _useStore, useSelector } from '@tanstack/react-store' // Optionally, you can create stores outside of React components at module scope const petStore = new Store( @@ -7,19 +7,20 @@ const petStore = new Store( cats: 0, dogs: 0, }, - ({ set }) => + ({ setState, get }) => // optionally, define actions for updating your store in specific ways right on the store. ({ addCat: () => - set((prev) => ({ + setState((prev) => ({ ...prev, cats: prev.cats + 1, })), addDog: () => - set((prev) => ({ + setState((prev) => ({ ...prev, dogs: prev.dogs + 1, })), + log: () => console.log(get()), }), ) @@ -29,44 +30,44 @@ function App() { return (
+

React Store Actions

This example creates a module-level store with actions. Components read - state with `useSelector` and call mutations through `useStoreActions`. + state with useSelector and call mutations through{' '} + store.actions or the experimental _useStore{' '} + hook.

- - + + -
) } -function CatsCard() { - // read state slice (only re-renders when the selected value changes) - const value = useSelector(petStore, (state) => state.cats) +function CatVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [cats, { addCat }] = _useStore(petStore, (state) => state.cats) - return

Cats: {value}

-} - -function DogsCard() { - // read state slice (only re-renders when the selected value changes) - const value = useSelector(petStore, (state) => state.dogs) - - return

Dogs: {value}

+ return ( +
+

Cats: {cats}

+ +
+ ) } -function StoreButtons() { - // pull stable action functions from the - const { addCat, addDog } = useStoreActions(petStore) +function DogVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [dogs, { addDog }] = _useStore(petStore, (state) => state.dogs) return (
- +

Dogs: {dogs}

) diff --git a/examples/react/store-context/src/index.tsx b/examples/react/store-context/src/index.tsx index 63e8f67c..2c2add5c 100644 --- a/examples/react/store-context/src/index.tsx +++ b/examples/react/store-context/src/index.tsx @@ -5,7 +5,6 @@ import { createStoreContext, useCreateStore, useSelector, - useSetValue, useValue, } from '@tanstack/react-store' import type { Atom, Store } from '@tanstack/react-store' @@ -97,14 +96,13 @@ function AtomSummary() { function NestedAtomControls() { const { countAtom } = useStoreContext() - const setCount = useSetValue(countAtom) return (
- -
@@ -127,14 +125,13 @@ function DeepAtomEditor() { function StoreButtons() { const { votesStore } = useStoreContext() - const setVotes = useSetValue(votesStore) return (
-
) diff --git a/examples/solid/store-actions/README.md b/examples/solid/store-actions/README.md index 690a3dbc..2b3d74e4 100644 --- a/examples/solid/store-actions/README.md +++ b/examples/solid/store-actions/README.md @@ -3,7 +3,7 @@ This example demonstrates: - `useSelector` -- `useStoreActions` +- `_useStore` - module-level `Store` actions To run this example: diff --git a/examples/solid/store-actions/src/index.tsx b/examples/solid/store-actions/src/index.tsx index d5597a38..5f78c86c 100644 --- a/examples/solid/store-actions/src/index.tsx +++ b/examples/solid/store-actions/src/index.tsx @@ -1,5 +1,5 @@ import { render } from 'solid-js/web' -import { Store, useSelector, useStoreActions } from '@tanstack/solid-store' +import { Store, _useStore, useSelector } from '@tanstack/solid-store' // Optionally, you can create stores outside of Solid components at module scope const petStore = new Store( @@ -7,63 +7,66 @@ const petStore = new Store( cats: 0, dogs: 0, }, - ({ set }) => + ({ setState, get }) => // optionally, define actions for updating your store in specific ways right on the store. ({ addCat: () => - set((prev) => ({ + setState((prev) => ({ ...prev, cats: prev.cats + 1, })), addDog: () => - set((prev) => ({ + setState((prev) => ({ ...prev, dogs: prev.dogs + 1, })), + log: () => console.log(get()), }), ) function App() { return (
+

Solid Store Actions

This example creates a module-level store with actions. Components read - state with `useSelector` and call mutations through `useStoreActions`. + state with useSelector and call mutations through{' '} + store.actions or the experimental _useStore{' '} + hook.

- - + + -
) } -function CatsCard() { - // read state slice (only re-renders when the selected value changes) - const value = useSelector(petStore, (state) => state.cats) - - return

Cats: {value()}

-} - -function DogsCard() { - // read state slice (only re-renders when the selected value changes) - const value = useSelector(petStore, (state) => state.dogs) +function CatVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [cats, { addCat }] = _useStore(petStore, (state) => state.cats) - return

Dogs: {value()}

+ return ( +
+

Cats: {cats()}

+ +
+ ) } -function StoreButtons() { - // pull stable action functions from the store - const { addCat, addDog } = useStoreActions(petStore) +function DogVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [dogs, { addDog }] = _useStore(petStore, (state) => state.dogs) return (
- +

Dogs: {dogs()}

) diff --git a/examples/solid/store-context/src/index.tsx b/examples/solid/store-context/src/index.tsx index 9ab04826..0f3d3c27 100644 --- a/examples/solid/store-context/src/index.tsx +++ b/examples/solid/store-context/src/index.tsx @@ -5,7 +5,6 @@ import { Store, useAtom, useSelector, - useSetValue, useValue, } from '@tanstack/solid-store' import type { Atom } from '@tanstack/solid-store' @@ -102,14 +101,13 @@ function AtomSummary() { function NestedAtomControls() { const { countAtom } = useStoreContext() - const setCount = useSetValue(countAtom) return (
- -
@@ -132,14 +130,13 @@ function DeepAtomEditor() { function StoreButtons() { const { votesStore } = useStoreContext() - const setVotes = useSetValue(votesStore) return (
- +

Editable count: {editableCount.current}

diff --git a/examples/svelte/store-actions/README.md b/examples/svelte/store-actions/README.md index 128fa117..9b0ca088 100644 --- a/examples/svelte/store-actions/README.md +++ b/examples/svelte/store-actions/README.md @@ -3,7 +3,7 @@ This example demonstrates: - `useSelector` -- `useStoreActions` +- `_useStore` - module-level `Store` actions To run this example: diff --git a/examples/svelte/store-actions/src/App.svelte b/examples/svelte/store-actions/src/App.svelte index d44faf06..eaddd233 100644 --- a/examples/svelte/store-actions/src/App.svelte +++ b/examples/svelte/store-actions/src/App.svelte @@ -1,5 +1,5 @@
+

Svelte Store Actions

This example creates a module-level store with actions. Components read - state with `useSelector` and call mutations through `useStoreActions`. + state with useSelector and call mutations through + store.actions or the experimental _useStore + hook.

-

Cats: {cats.current}

-

Dogs: {dogs.current}

-

Total votes: {total.current}

- - +

Cats: {cats.current}

+ +
+
+

Dogs: {dogs.current}

+
+

Total votes: {total.current}

diff --git a/examples/svelte/store-context/src/AtomSection.svelte b/examples/svelte/store-context/src/AtomSection.svelte index 392dd9c3..847eb952 100644 --- a/examples/svelte/store-context/src/AtomSection.svelte +++ b/examples/svelte/store-context/src/AtomSection.svelte @@ -1,13 +1,12 @@ @@ -15,8 +14,10 @@

Nested Atom Components

Atom count: {count.current}

- - + +

Editable atom count: {editableCount.current}

diff --git a/examples/svelte/store-context/src/StoreSection.svelte b/examples/svelte/store-context/src/StoreSection.svelte index 484b0550..9e86e01c 100644 --- a/examples/svelte/store-context/src/StoreSection.svelte +++ b/examples/svelte/store-context/src/StoreSection.svelte @@ -1,6 +1,6 @@

Cats: {cats.current}

@@ -20,7 +19,7 @@
- +

Editable count: {{ editableCount }}

diff --git a/examples/vue/store-actions/README.md b/examples/vue/store-actions/README.md index f9c74683..48758d4e 100644 --- a/examples/vue/store-actions/README.md +++ b/examples/vue/store-actions/README.md @@ -3,7 +3,7 @@ This example demonstrates: - `useSelector` -- `useStoreActions` +- `_useStore` - module-level `Store` actions To run this example: diff --git a/examples/vue/store-actions/src/App.vue b/examples/vue/store-actions/src/App.vue index b32c02f5..942abe52 100644 --- a/examples/vue/store-actions/src/App.vue +++ b/examples/vue/store-actions/src/App.vue @@ -1,5 +1,5 @@ diff --git a/examples/vue/store-context/src/App.vue b/examples/vue/store-context/src/App.vue index 7d53d86f..5938e9a3 100644 --- a/examples/vue/store-context/src/App.vue +++ b/examples/vue/store-context/src/App.vue @@ -5,7 +5,6 @@ import { Store, useAtom, useSelector, - useSetValue, useValue, } from '@tanstack/vue-store' import type { Atom } from '@tanstack/vue-store' @@ -79,16 +78,22 @@ const AtomSummary = defineComponent(() => { const NestedAtomControls = defineComponent(() => { const { countAtom } = useStoreContext() - const setCount = useSetValue(countAtom) return () => h('div', [ h( 'button', - { type: 'button', onClick: () => setCount((prev: number) => prev + 1) }, + { + type: 'button', + onClick: () => countAtom.set((prev: number) => prev + 1), + }, 'Increment atom', ), - h('button', { type: 'button', onClick: () => setCount(0) }, 'Reset atom'), + h( + 'button', + { type: 'button', onClick: () => countAtom.set(0) }, + 'Reset atom', + ), ]) }) @@ -109,7 +114,6 @@ const DeepAtomEditor = defineComponent(() => { const StoreButtons = defineComponent(() => { const { votesStore } = useStoreContext() - const setVotes = useSetValue(votesStore) return () => h('div', [ @@ -118,7 +122,7 @@ const StoreButtons = defineComponent(() => { { type: 'button', onClick: () => - setVotes((prev: CounterStore) => ({ + votesStore.setState((prev: CounterStore) => ({ ...prev, cats: prev.cats + 1, })), @@ -130,7 +134,7 @@ const StoreButtons = defineComponent(() => { { type: 'button', onClick: () => - setVotes((prev: CounterStore) => ({ + votesStore.setState((prev: CounterStore) => ({ ...prev, dogs: prev.dogs + 1, })), diff --git a/packages/angular-store/src/_injectStore.ts b/packages/angular-store/src/_injectStore.ts new file mode 100644 index 00000000..2a6f9714 --- /dev/null +++ b/packages/angular-store/src/_injectStore.ts @@ -0,0 +1,41 @@ +import { injectSelector } from './injectSelector' +import type { Signal } from '@angular/core' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { InjectSelectorOptions } from './injectSelector' + +/** + * Experimental combined read+write injection function for stores, mirroring + * injectAtom's pattern. + * + * Returns `[signal, actions]` when the store has an actions factory, or + * `[signal, setState]` for plain stores. + * + * @example + * ```ts + * // Store with actions + * readonly result = _injectStore(petStore, (s) => s.cats) + * // result[0] is Signal, result[1] is actions + * + * // Store without actions + * readonly result = _injectStore(plainStore, (s) => s) + * // result[0] is Signal, result[1] is setState + * ``` + */ +export function _injectStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: InjectSelectorOptions, +): [ + Signal, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = injectSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/angular-store/src/createStoreContext.ts b/packages/angular-store/src/createStoreContext.ts new file mode 100644 index 00000000..e724713c --- /dev/null +++ b/packages/angular-store/src/createStoreContext.ts @@ -0,0 +1,71 @@ +import { InjectionToken, inject } from '@angular/core' +import type { Provider } from '@angular/core' + +/** + * Creates a typed Angular dependency-injection context for sharing a bundle of + * atoms and stores with a component subtree. + * + * The returned `provideStoreContext` function accepts a factory that creates the + * context value. Using a factory (rather than a static value) ensures each + * component instance — and each SSR request — receives its own state, avoiding + * cross-request pollution. + * + * Consumers call `injectStoreContext()` inside an injection context (typically a + * constructor or field initializer) to retrieve the contextual atoms and stores, + * then compose them with existing hooks like {@link injectSelector}, + * {@link injectValue}, and {@link injectAtom}. + * + * @example + * ```ts + * const { provideStoreContext, injectStoreContext } = createStoreContext<{ + * countAtom: Atom + * totalsStore: Store<{ count: number }> + * }>() + * + * // Parent component provides the context + * @Component({ + * providers: [ + * provideStoreContext(() => ({ + * countAtom: createAtom(0), + * totalsStore: new Store({ count: 0 }), + * })), + * ], + * template: ``, + * }) + * class ParentComponent {} + * + * // Child component consumes the context + * @Component({ template: `{{ count() }}` }) + * class ChildComponent { + * private ctx = injectStoreContext() + * count = injectValue(this.ctx.countAtom) + * } + * ``` + * + * @throws When `injectStoreContext()` is called without a matching + * `provideStoreContext()` in a parent component's providers. + */ +export function createStoreContext(): { + provideStoreContext: (factory: () => TValue) => Provider + injectStoreContext: () => TValue +} { + const token = new InjectionToken('StoreContext') + + function provideStoreContext(factory: () => TValue): Provider { + return { provide: token, useFactory: factory } + } + + function injectStoreContext(): TValue { + const value = inject(token, { optional: true }) + + if (value === null) { + throw new Error( + "Missing StoreContext provider. Add provideStoreContext() to a parent component's providers array.", + ) + } + + return value + } + + return { provideStoreContext, injectStoreContext } +} diff --git a/packages/angular-store/src/index.ts b/packages/angular-store/src/index.ts index 6f4cebb0..0164d1cd 100644 --- a/packages/angular-store/src/index.ts +++ b/packages/angular-store/src/index.ts @@ -1,314 +1,10 @@ -import { - DestroyRef, - Injector, - assertInInjectionContext, - inject, - linkedSignal, - runInInjectionContext, -} from '@angular/core' -import type { CreateSignalOptions, Signal } from '@angular/core' -import type { - Atom, - ReadonlyAtom, - ReadonlyStore, - Store, - StoreActionMap, -} from '@tanstack/store' - export * from '@tanstack/store' -export interface InjectSelectorOptions extends Omit< - CreateSignalOptions, - 'equal' -> { - compare?: (a: TSelected, b: TSelected) => boolean - injector?: Injector -} - -type CompatibilityInjectStoreOptions = - CreateSignalOptions & { - injector?: Injector - } - -type SelectionSource = { - get: () => T - subscribe: (listener: (value: T) => void) => { - unsubscribe: () => void - } -} - -function defaultCompare(a: T, b: T) { - return a === b -} - -function resolveInjector( - fn: (...args: Array) => unknown, - injector?: Injector, -) { - if (!injector) { - assertInInjectionContext(fn) - return inject(Injector) - } - - return injector -} - -function createReadonlySelectionSignal( - source: SelectionSource, - selector: (state: NoInfer) => TSelected, - options?: InjectSelectorOptions, -): Signal { - const injector = resolveInjector( - createReadonlySelectionSignal, - options?.injector, - ) - - return runInInjectionContext(injector, () => { - const destroyRef = inject(DestroyRef) - const compare = options?.compare ?? defaultCompare - const { - injector: _injector, - compare: _compare, - ...signalOptions - } = options ?? {} - const slice = linkedSignal(() => selector(source.get()), { - ...signalOptions, - equal: compare, - }) - - const { unsubscribe } = source.subscribe((state) => { - slice.set(selector(state)) - }) - - destroyRef.onDestroy(() => { - unsubscribe() - }) - - return slice.asReadonly() - }) -} - -/** - * Selects a slice of state from an atom or store and returns it as an Angular - * signal. - * - * This is the primary Angular read hook for TanStack Store. - * - * @example - * ```ts - * readonly count = injectSelector(counterStore, (state) => state.count) - * ``` - * - * @example - * ```ts - * readonly doubled = injectSelector(countAtom, (value) => value * 2) - * ``` - */ -export function injectSelector>( - source: SelectionSource, - selector: (state: NoInfer) => TSelected = (d) => - d as unknown as TSelected, - options?: InjectSelectorOptions, -): Signal { - return createReadonlySelectionSignal(source, selector, options) -} - -/** - * Returns the current value signal for an atom or store. - * - * This is the whole-value counterpart to {@link injectSelector}. - * - * @example - * ```ts - * readonly count = injectValue(countAtom) - * ``` - * - * @example - * ```ts - * readonly state = injectValue(counterStore) - * ``` - */ -export function injectValue( - source: - | Atom - | ReadonlyAtom - | Store - | ReadonlyStore, - options?: InjectSelectorOptions, -): Signal { - return injectSelector(source, (value) => value, options) -} - -/** - * Returns a stable setter for a writable atom or store. - * - * Writable atoms preserve their native `set` contract. Writable stores - * preserve their native `setState` contract. - * - * @example - * ```ts - * readonly setCount = injectSetValue(countAtom) - * - * increment() { - * this.setCount((prev) => prev + 1) - * } - * ``` - * - * @example - * ```ts - * readonly setState = injectSetValue(counterStore) - * ``` - */ -export function injectSetValue( - source: Atom, -): Atom['set'] -export function injectSetValue( - source: Store, -): Store['setState'] -export function injectSetValue( - source: Atom | Store, -) { - return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { - if ('setState' in source) { - source.setState(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - if (typeof valueOrUpdater === 'function') { - source.set(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - source.set(valueOrUpdater) - } - } - }) as Atom['set'] | Store['setState'] -} - -/** - * Returns the current atom signal together with a setter. - * - * Use this when a component needs to both read and update the same writable - * atom. - * - * @example - * ```ts - * readonly atomTuple = injectAtom(countAtom) - * readonly count = this.atomTuple[0] - * readonly setCount = this.atomTuple[1] - * ``` - */ -export function injectAtom( - atom: Atom, - options?: InjectSelectorOptions, -): [Signal, Atom['set']] { - const value = injectValue(atom, options) - const setValue = injectSetValue(atom) - - return [value, setValue] -} - -/** - * Returns the stable actions bag from a writable store created with actions. - * - * Use this when a component only needs to call store actions and should not - * subscribe to store state. - * - * @example - * ```ts - * readonly actions = injectStoreActions(counterStore) - * - * increment() { - * this.actions.increment() - * } - * ``` - */ -export function injectStoreActions( - store: Store, -): TActions { - return store.actions -} - -/** - * Deprecated alias for {@link injectSelector}. - * - * @example - * ```ts - * readonly count = injectStore(counterStore, (state) => state.count) - * ``` - * - * @deprecated Use `injectSelector` instead. - */ -export function injectStore>( - store: SelectionSource, - selector?: (state: NoInfer) => TSelected, - options?: CompatibilityInjectStoreOptions, -): Signal -export function injectStore>( - store: SelectionSource, - selector: (state: NoInfer) => TSelected = (d) => - d as unknown as TSelected, - options?: CompatibilityInjectStoreOptions, -): Signal { - const { equal, injector, ...signalOptions } = options ?? {} - - return injectSelector(store, selector, { - ...signalOptions, - compare: equal, - injector, - }) -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } +export * from './injectSelector' +export * from './injectValue' - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +export * from './injectAtom' +export * from './injectStore' // @deprecated in favor of injectSelector +export * from './_injectStore' -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} +export * from './createStoreContext' diff --git a/packages/angular-store/src/injectAtom.ts b/packages/angular-store/src/injectAtom.ts new file mode 100644 index 00000000..ef28257e --- /dev/null +++ b/packages/angular-store/src/injectAtom.ts @@ -0,0 +1,52 @@ +import { injectValue } from './injectValue' +import type { Atom } from '@tanstack/store' +import type { InjectSelectorOptions } from './injectSelector' + +/** + * A callable signal that reads the current atom value when invoked and + * exposes a `.set` method matching the atom's native setter contract. + * + * This is the Angular-idiomatic return type for {@link injectAtom}. It can + * be used as a class property and called directly in templates. + * + * @example + * ```ts + * readonly count = injectAtom(countAtom) + * + * // read in template: {{ count() }} + * // write in class: this.count.set(5) + * // this.count.set(prev => prev + 1) + * ``` + */ +export interface WritableAtomSignal { + /** Read the current value. */ + (): T + /** Set the atom value (accepts a direct value or an updater function). */ + set: Atom['set'] +} + +/** + * Returns a {@link WritableAtomSignal} that reads the current atom value when + * called and exposes a `.set` method for updates. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * readonly count = injectAtom(countAtom) + * + * increment() { + * this.count.set((prev) => prev + 1) + * } + * ``` + */ +export function injectAtom( + atom: Atom, + options?: InjectSelectorOptions, +): WritableAtomSignal { + const value = injectValue(atom, options) + const atomSignal = (() => value()) as WritableAtomSignal + atomSignal.set = atom.set + return atomSignal +} diff --git a/packages/angular-store/src/injectSelector.ts b/packages/angular-store/src/injectSelector.ts new file mode 100644 index 00000000..d9b0e249 --- /dev/null +++ b/packages/angular-store/src/injectSelector.ts @@ -0,0 +1,100 @@ +import { + DestroyRef, + Injector, + assertInInjectionContext, + inject, + linkedSignal, + runInInjectionContext, +} from '@angular/core' +import type { CreateSignalOptions, Signal } from '@angular/core' + +export interface InjectSelectorOptions extends Omit< + CreateSignalOptions, + 'equal' +> { + compare?: (a: TSelected, b: TSelected) => boolean + injector?: Injector +} + +export type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +function resolveInjector( + fn: (...args: Array) => unknown, + injector?: Injector, +) { + if (!injector) { + assertInInjectionContext(fn) + return inject(Injector) + } + + return injector +} + +function createReadonlySelectionSignal( + source: SelectionSource, + selector: (state: NoInfer) => TSelected, + options?: InjectSelectorOptions, +): Signal { + const injector = resolveInjector( + createReadonlySelectionSignal, + options?.injector, + ) + + return runInInjectionContext(injector, () => { + const destroyRef = inject(DestroyRef) + const compare = options?.compare ?? defaultCompare + const { + injector: _injector, + compare: _compare, + ...signalOptions + } = options ?? {} + const slice = linkedSignal(() => selector(source.get()), { + ...signalOptions, + equal: compare, + }) + + const { unsubscribe } = source.subscribe((state) => { + slice.set(selector(state)) + }) + + destroyRef.onDestroy(() => { + unsubscribe() + }) + + return slice.asReadonly() + }) +} + +/** + * Selects a slice of state from an atom or store and returns it as an Angular + * signal. + * + * This is the primary Angular read hook for TanStack Store. + * + * @example + * ```ts + * readonly count = injectSelector(counterStore, (state) => state.count) + * ``` + * + * @example + * ```ts + * readonly doubled = injectSelector(countAtom, (value) => value * 2) + * ``` + */ +export function injectSelector>( + source: SelectionSource, + selector: (state: NoInfer) => TSelected = (d) => + d as unknown as TSelected, + options?: InjectSelectorOptions, +): Signal { + return createReadonlySelectionSignal(source, selector, options) +} diff --git a/packages/angular-store/src/injectStore.ts b/packages/angular-store/src/injectStore.ts new file mode 100644 index 00000000..360492d1 --- /dev/null +++ b/packages/angular-store/src/injectStore.ts @@ -0,0 +1,38 @@ +import { injectSelector } from './injectSelector' +import type { CreateSignalOptions, Injector, Signal } from '@angular/core' +import type { SelectionSource } from './injectSelector' + +type CompatibilityInjectStoreOptions = + CreateSignalOptions & { + injector?: Injector + } + +/** + * Deprecated alias for {@link injectSelector}. + * + * @example + * ```ts + * readonly count = injectStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `injectSelector` instead. + */ +export function injectStore>( + store: SelectionSource, + selector?: (state: NoInfer) => TSelected, + options?: CompatibilityInjectStoreOptions, +): Signal +export function injectStore>( + store: SelectionSource, + selector: (state: NoInfer) => TSelected = (d) => + d as unknown as TSelected, + options?: CompatibilityInjectStoreOptions, +): Signal { + const { equal, injector, ...signalOptions } = options ?? {} + + return injectSelector(store, selector, { + ...signalOptions, + compare: equal, + injector, + }) +} diff --git a/packages/angular-store/src/injectValue.ts b/packages/angular-store/src/injectValue.ts new file mode 100644 index 00000000..ea20b78c --- /dev/null +++ b/packages/angular-store/src/injectValue.ts @@ -0,0 +1,30 @@ +import { injectSelector } from './injectSelector' +import type { Signal } from '@angular/core' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { InjectSelectorOptions } from './injectSelector' + +/** + * Returns the current value signal for an atom or store. + * + * This is the whole-value counterpart to {@link injectSelector}. + * + * @example + * ```ts + * readonly count = injectValue(countAtom) + * ``` + * + * @example + * ```ts + * readonly state = injectValue(counterStore) + * ``` + */ +export function injectValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: InjectSelectorOptions, +): Signal { + return injectSelector(source, (value) => value, options) +} diff --git a/packages/angular-store/tests/index.test.ts b/packages/angular-store/tests/index.test.ts index 5645f975..b25558db 100644 --- a/packages/angular-store/tests/index.test.ts +++ b/packages/angular-store/tests/index.test.ts @@ -2,15 +2,16 @@ import { describe, expect, test } from 'vitest' import { Component, effect } from '@angular/core' import { TestBed } from '@angular/core/testing' import { By } from '@angular/platform-browser' -import { createAtom, createStore } from '@tanstack/store' +import { Store, createAtom, createStore } from '@tanstack/store' import { + _injectStore, + createStoreContext, injectAtom, injectSelector, - injectSetValue, injectStore, - injectStoreActions, injectValue, } from '../src/index' +import type { Atom } from '@tanstack/store' describe('atom hooks', () => { test('injectValue reads mutable atom state and rerenders when updated', () => { @@ -46,28 +47,23 @@ describe('atom hooks', () => { expect(fixture.nativeElement.textContent).toContain('Value: 1') }) - test('injectAtom returns the current signal and setter', () => { + test('injectAtom returns a callable signal with a set method', () => { const atom = createAtom(0) @Component({ template: `
-

Value: {{ value() }}

+

Value: {{ count() }}

`, standalone: true, }) class MyCmp { - value!: ReturnType>[0] - setValue!: ReturnType>[1] - - constructor() { - ;[this.value, this.setValue] = injectAtom(atom) - } + count = injectAtom(atom) add() { - this.setValue((prev) => prev + 5) + this.count.set((prev) => prev + 5) } } @@ -83,6 +79,43 @@ describe('atom hooks', () => { expect(fixture.nativeElement.textContent).toContain('Value: 5') }) + + test('injectAtom set accepts a direct value', () => { + const atom = createAtom(0) + + @Component({ + template: ` +
+

Value: {{ count() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + count = injectAtom(atom) + + constructor() { + this.count.set(42) + } + + reset() { + this.count.set(0) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 42') + + fixture.debugElement + .query(By.css('button#reset')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 0') + }) }) describe('selector hooks', () => { @@ -332,44 +365,91 @@ describe('selector hooks', () => { fixture.debugElement.query(By.css('p#derived')).nativeElement.textContent, ).toContain('2') }) +}) - test('injectSetValue updates stores by updater', () => { - const store = createStore(0) +describe('injectStore', () => { + test('is a compatibility alias for injectSelector', () => { + const store = createStore({ select: 0 }) @Component({ - template: ``, + template: `

Store: {{ storeVal() }}

`, standalone: true, }) class MyCmp { - updateState = injectSetValue(store) + storeVal = injectStore(store, (state) => state.select) + } - update() { - this.updateState((prev) => prev + 1) + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Store: 0') + }) +}) + +describe('dataType', () => { + test('date change trigger re-render', () => { + const store = createStore({ date: new Date('2025-03-29T21:06:30.401Z') }) + + @Component({ + template: ` +
+

{{ storeVal() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + storeVal = injectSelector(store, (state) => state.date) + + updateDate() { + store.setState((v) => ({ + ...v, + date: new Date('2025-03-29T21:06:40.401Z'), + })) } } const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement + .textContent, + ).toContain(new Date('2025-03-29T21:06:30.401Z')) + fixture.debugElement - .query(By.css('button#update')) + .query(By.css('button#updateDate')) .triggerEventHandler('click', null) - expect(store.state).toBe(1) + fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement + .textContent, + ).toContain(new Date('2025-03-29T21:06:40.401Z')) }) +}) - test('injectStoreActions returns the stable actions bag', () => { - const store = createStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), +describe('_injectStore', () => { + test('returns selected state and actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) @Component({ - template: ``, + template: ` +
+

{{ count() }}

+ +
+ `, standalone: true, }) class MyCmp { - actions = injectStoreActions(store) + private result = _injectStore(store, (state) => state.count) + count = this.result[0] + actions = this.result[1] - update() { + inc() { this.actions.inc() } } @@ -377,52 +457,97 @@ describe('selector hooks', () => { const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('0') + fixture.debugElement - .query(By.css('button#update')) + .query(By.css('button#inc')) .triggerEventHandler('click', null) - expect(store.state.count).toBe(1) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('1') }) -}) -describe('injectStore', () => { - test('is a compatibility alias for injectSelector', () => { - const store = createStore({ select: 0 }) + test('returns selected state and setState for plain stores', () => { + const store = createStore(0) @Component({ - template: `

Store: {{ storeVal() }}

`, + template: ` +
+

{{ value() }}

+ +
+ `, standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.select) + private result = _injectStore(store, (state) => state) + value = this.result[0] + setState = this.result[1] + + inc() { + this.setState((prev) => prev + 1) + } } const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - expect(fixture.nativeElement.textContent).toContain('Store: 0') + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('0') + + fixture.debugElement + .query(By.css('button#inc')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('1') }) }) -describe('dataType', () => { - test('date change trigger re-render', () => { - const store = createStore({ date: new Date('2025-03-29T21:06:30.401Z') }) +describe('createStoreContext', () => { + test('provides and injects a typed store context', () => { + const { provideStoreContext, injectStoreContext } = createStoreContext<{ + countAtom: Atom + petStore: Store<{ cats: number; dogs: number }> + }>() @Component({ template: `
-

{{ storeVal() }}

- +

{{ count() }}

+

{{ cats() }}

+ +
`, standalone: true, + providers: [ + provideStoreContext(() => ({ + countAtom: createAtom(10), + petStore: new Store({ cats: 2, dogs: 3 }), + })), + ], }) class MyCmp { - storeVal = injectSelector(store, (state) => state.date) + private ctx = injectStoreContext() + count = injectValue(this.ctx.countAtom) + cats = injectSelector(this.ctx.petStore, (s) => s.cats) - updateDate() { - store.setState((v) => ({ - ...v, - date: new Date('2025-03-29T21:06:40.401Z'), + inc() { + this.ctx.countAtom.set((prev) => prev + 1) + } + + addCat() { + this.ctx.petStore.setState((prev) => ({ + ...prev, + cats: prev.cats + 1, })) } } @@ -431,17 +556,45 @@ describe('dataType', () => { fixture.detectChanges() expect( - fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement - .textContent, - ).toContain(new Date('2025-03-29T21:06:30.401Z')) + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('10') + expect( + fixture.debugElement.query(By.css('p#cats')).nativeElement.textContent, + ).toContain('2') fixture.debugElement - .query(By.css('button#updateDate')) + .query(By.css('button#inc')) .triggerEventHandler('click', null) fixture.detectChanges() expect( - fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement - .textContent, - ).toContain(new Date('2025-03-29T21:06:40.401Z')) + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('11') + + fixture.debugElement + .query(By.css('button#addCat')) + .triggerEventHandler('click', null) + fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#cats')).nativeElement.textContent, + ).toContain('3') + }) + + test('throws when injectStoreContext is called without a provider', () => { + const { injectStoreContext } = createStoreContext<{ + countAtom: Atom + }>() + + @Component({ + template: `

{{ count() }}

`, + standalone: true, + }) + class MyCmp { + private ctx = injectStoreContext() + count = injectValue(this.ctx.countAtom) + } + + expect(() => TestBed.createComponent(MyCmp)).toThrow( + /Missing StoreContext provider/, + ) }) }) diff --git a/packages/angular-store/tests/test.test-d.ts b/packages/angular-store/tests/test.test-d.ts index c04b1668..8db1b503 100644 --- a/packages/angular-store/tests/test.test-d.ts +++ b/packages/angular-store/tests/test.test-d.ts @@ -1,15 +1,16 @@ import { expectTypeOf, test } from 'vitest' import { createAtom, createStore } from '@tanstack/store' import { + _injectStore, + createStoreContext, injectAtom, injectSelector, - injectSetValue, injectStore, - injectStoreActions, injectValue, } from '../src' import type { Signal } from '@angular/core' -import type { Atom } from '@tanstack/store' +import type { Atom, Store } from '@tanstack/store' +import type { WritableAtomSignal } from '../src' test('injectSelector works with derived state', () => { const store = createStore(12) @@ -35,32 +36,16 @@ test('injectValue infers value from mutable and readonly sources', () => { expectTypeOf(injectValue(readonlyStore)).toEqualTypeOf>() }) -test('injectSetValue preserves native setter contracts', () => { +test('injectAtom returns a WritableAtomSignal', () => { const writableAtom = createAtom(12) const readonlyAtom = createAtom(() => 24) - const writableStore = createStore(12) - const readonlyStore = createStore(() => 24) - expectTypeOf(injectSetValue(writableAtom)).toEqualTypeOf< - Atom['set'] - >() - expectTypeOf(injectSetValue(writableStore)).toEqualTypeOf< - typeof writableStore.setState - >() - // @ts-expect-error readonly atoms cannot be set - injectSetValue(readonlyAtom) - // @ts-expect-error readonly stores cannot be set - injectSetValue(readonlyStore) -}) + const atomSignal = injectAtom(writableAtom) -test('injectAtom only accepts writable atoms', () => { - const writableAtom = createAtom(12) - const readonlyAtom = createAtom(() => 24) - - const [value, setValue] = injectAtom(writableAtom) + expectTypeOf(atomSignal).toEqualTypeOf>() + expectTypeOf(atomSignal()).toEqualTypeOf() + expectTypeOf(atomSignal.set).toEqualTypeOf['set']>() - expectTypeOf(value).toEqualTypeOf>() - expectTypeOf(setValue).toBeFunction() // @ts-expect-error readonly atoms cannot be used with injectAtom injectAtom(readonlyAtom) }) @@ -74,21 +59,37 @@ test('injectStore matches injectSelector types for compatibility', () => { expectTypeOf(compatValue).toEqualTypeOf>() }) -test('injectStoreActions infers the action bag from writable stores', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - current: () => get().count, +test('createStoreContext preserves typed context shape', () => { + const { provideStoreContext, injectStoreContext } = createStoreContext<{ + countAtom: Atom + petStore: Store<{ cats: number }> + }>() + + expectTypeOf(provideStoreContext).toBeFunction() + expectTypeOf(injectStoreContext).toBeFunction() + + const ctx = injectStoreContext() + + expectTypeOf(ctx.countAtom).toEqualTypeOf>() + expectTypeOf(ctx.petStore).toEqualTypeOf>() +}) + +test('_injectStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) - const actions = injectStoreActions(store) + const [selected, actions] = _injectStore(store, (state) => state.count) + expectTypeOf(selected).toEqualTypeOf>() expectTypeOf(actions.inc).toBeFunction() - expectTypeOf(actions.current()).toExtend() +}) - const plainStore = createStore(12) - expectTypeOf(injectStoreActions(plainStore)).toEqualTypeOf() +test('_injectStore returns setState for plain stores', () => { + const store = createStore(0) - const readonlyStore = createStore(() => 24) - // @ts-expect-error readonly stores do not expose actions - injectStoreActions(readonlyStore) + const [selected, setState] = _injectStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf>() + expectTypeOf(setState).toEqualTypeOf['setState']>() }) diff --git a/packages/preact-store/src/_useStore.ts b/packages/preact-store/src/_useStore.ts new file mode 100644 index 00000000..89be003a --- /dev/null +++ b/packages/preact-store/src/_useStore.ts @@ -0,0 +1,43 @@ +import { useMemo } from 'preact/hooks' +import { useSelector } from './useSelector' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' // eslint-disable-line no-duplicate-imports + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```tsx + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +/* eslint-disable react-hooks/rules-of-hooks -- experimental API with underscore prefix */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + TSelected, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = useMemo( + () => (store.actions as StoreActionMap | undefined) ?? store.setState, + [store], + ) + + return [selected, actionsOrSetState] as any +} diff --git a/packages/preact-store/src/index.ts b/packages/preact-store/src/index.ts index 0716abab..4bcfb415 100644 --- a/packages/preact-store/src/index.ts +++ b/packages/preact-store/src/index.ts @@ -4,14 +4,9 @@ export * from './createStoreContext' export * from './useCreateAtom' export * from './useCreateStore' -export * from './useSetValue' -export * from './useStoreActions' - export * from './useValue' export * from './useSelector' export * from './useAtom' export * from './useStore' // @deprecated in favor of useSelector - -// comparators -export * from './shallow' +export * from './_useStore' diff --git a/packages/preact-store/src/useAtom.ts b/packages/preact-store/src/useAtom.ts index eb2b47f7..7492d000 100644 --- a/packages/preact-store/src/useAtom.ts +++ b/packages/preact-store/src/useAtom.ts @@ -1,4 +1,3 @@ -import { useSetValue } from './useSetValue' import { useValue } from './useValue' import type { Atom } from '@tanstack/store' import type { UseSelectorOptions } from './useSelector' @@ -25,7 +24,6 @@ export function useAtom( options?: UseSelectorOptions, ): [TValue, Atom['set']] { const value = useValue(atom, options) - const setValue = useSetValue(atom) - return [value, setValue] + return [value, atom.set] } diff --git a/packages/preact-store/src/useSetValue.ts b/packages/preact-store/src/useSetValue.ts deleted file mode 100644 index 4eeeb0a2..00000000 --- a/packages/preact-store/src/useSetValue.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useCallback } from 'preact/hooks' -import type { Atom, Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns a stable setter for a writable atom or store. - * - * Writable atoms preserve their native `set` contract. Writable stores - * preserve their native `setState` contract. - * - * @example - * ```tsx - * const setCount = useSetValue(countAtom) - * setCount((prev) => prev + 1) - * ``` - * - * @example - * ```tsx - * const setState = useSetValue(counterStore) - * setState((state) => ({ ...state, count: state.count + 1 })) - * ``` - */ -export function useSetValue(source: Atom): Atom['set'] -export function useSetValue( - source: Store, -): Store['setState'] -export function useSetValue( - source: Atom | Store, -) { - return useCallback['set'] | Store['setState']>( - (valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { - if ('setState' in source) { - source.setState(valueOrUpdater as (prevVal: TValue) => TValue) - } else if (typeof valueOrUpdater === 'function') { - source.set(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - source.set(valueOrUpdater) - } - }, - [source], - ) -} diff --git a/packages/preact-store/src/useStoreActions.ts b/packages/preact-store/src/useStoreActions.ts deleted file mode 100644 index 0e4996c3..00000000 --- a/packages/preact-store/src/useStoreActions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useMemo } from 'preact/hooks' -import type { Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns the stable actions bag from a writable store created with actions. - * - * Use this when a component only needs to call store actions and should not - * subscribe to store state. - * - * @example - * ```tsx - * const actions = useStoreActions(counterStore) - * actions.increment() - * ``` - */ -export function useStoreActions( - store: Store, -): TActions { - return useMemo(() => store.actions, [store]) -} diff --git a/packages/preact-store/tests/index.test.tsx b/packages/preact-store/tests/index.test.tsx index 4b01693b..2f66e036 100644 --- a/packages/preact-store/tests/index.test.tsx +++ b/packages/preact-store/tests/index.test.tsx @@ -3,15 +3,14 @@ import { userEvent } from '@testing-library/user-event' import { describe, expect, it, test, vi } from 'vitest' import { createAtom, createStore } from '@tanstack/store' import { + _useStore, createStoreContext, shallow, useAtom, useCreateAtom, useCreateStore, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src/index' @@ -143,25 +142,6 @@ describe('atom hooks', () => { expect(getByText('Renders: 2')).toBeInTheDocument() }) - it('useSetValue updates atoms by value and updater and stays stable', () => { - const atom = createAtom(0) - const { result, rerender } = renderHook(() => useSetValue(atom)) - const setAtom = result.current - - act(() => { - result.current(1) - }) - expect(atom.get()).toBe(1) - - rerender() - expect(result.current).toBe(setAtom) - - act(() => { - result.current((prev) => prev + 1) - }) - expect(atom.get()).toBe(2) - }) - it('useAtom returns the current value and setter', () => { const atom = createAtom(0) const { result } = renderHook(() => useAtom(atom)) @@ -174,25 +154,6 @@ describe('atom hooks', () => { expect(result.current[0]).toBe(5) }) - - it('useSetValue updates stores by updater and stays stable', () => { - const store = createStore(0) - const { result, rerender } = renderHook(() => useSetValue(store)) - const setStore = result.current - - act(() => { - result.current((prev) => prev + 1) - }) - expect(store.state).toBe(1) - - rerender() - expect(result.current).toBe(setStore) - - act(() => { - result.current((prev) => prev + 1) - }) - expect(store.state).toBe(2) - }) }) describe('store contexts', () => { @@ -208,21 +169,22 @@ describe('store contexts', () => { const { countAtom: currentAtom, totalStore: currentStore } = useStoreContext() const value = useValue(currentAtom) - const setValue = useSetValue(currentAtom) const total = useSelector(currentStore, (state) => state.count) - const setTotal = useSetValue(currentStore) return (

Value: {value}

Total: {total}

- +
+ ) + } + + const { getByText } = render() + expect(getByText('Count: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument()) + }) + + it('returns selected state and setState for plain stores', async () => { + const store = createStore(0) + + function Comp() { + const [value, setState] = _useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + describe('shallow', () => { test('should return true for shallowly equal objects', () => { const objA = { a: 1, b: 'hello' } diff --git a/packages/preact-store/tests/test.test-d.ts b/packages/preact-store/tests/test.test-d.ts index 6d291fd1..95b1c310 100644 --- a/packages/preact-store/tests/test.test-d.ts +++ b/packages/preact-store/tests/test.test-d.ts @@ -1,18 +1,17 @@ import { expectTypeOf, test } from 'vitest' import { createAtom, createStore } from '@tanstack/store' import { + _useStore, createStoreContext, useAtom, useCreateAtom, useCreateStore, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src' // eslint-disable-next-line no-duplicate-imports -import type { Atom, ReadonlyStore } from '@tanstack/store' +import type { Atom, ReadonlyStore, Store } from '@tanstack/store' test('useCreateAtom returns a writable atom for initial values', () => { const atom = useCreateAtom(12) @@ -47,22 +46,6 @@ test('useValue infers value from mutable and readonly atoms', () => { ).toExtend() }) -test('useSetValue preserves native atom and store setter contracts', () => { - const writableAtom = createAtom(12) - const readonlyAtom = createAtom(() => 24) - const writableStore = createStore(12) - const readonlyStore = createStore(() => 24) - - expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() - expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< - typeof writableStore.setState - >() - // @ts-expect-error readonly atoms cannot be set - useSetValue(readonlyAtom) - // @ts-expect-error readonly stores cannot be set - useSetValue(readonlyStore) -}) - test('useAtom only accepts writable atoms', () => { const writableAtom = createAtom(12) const readonlyAtom = createAtom(() => 24) @@ -81,9 +64,12 @@ test('useAtom only accepts writable atoms', () => { test('useCreateStore returns writable and readonly store types', () => { const writableStore = useCreateStore(12) - const writableStoreWithActions = useCreateStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - })) + const writableStoreWithActions = useCreateStore( + { count: 0 }, + ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + }), + ) const readonlyStore = useCreateStore(() => 24) expectTypeOf(writableStore.state).toExtend() @@ -148,25 +134,6 @@ test('useStore matches useSelector types for compatibility', () => { expectTypeOf(compatValue).toExtend() }) -test('useStoreActions infers the action bag from writable stores', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - current: () => get().count, - })) - - const actions = useStoreActions(store) - - expectTypeOf(actions.inc).toBeFunction() - expectTypeOf(actions.current()).toExtend() - - const plainStore = createStore(12) - expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() - - const readonlyStore = createStore(() => 24) - // @ts-expect-error readonly stores do not expose actions - useStoreActions(readonlyStore) -}) - test('createStoreContext preserves keyed atom and store types', () => { const countAtom = createAtom(12) const readonlySource = createStore(() => ({ value: 24 })) @@ -178,7 +145,6 @@ test('createStoreContext preserves keyed atom and store types', () => { expectTypeOf(contextValue.countAtom).toExtend>() expectTypeOf(contextValue.countAtom.set).toBeFunction() - expectTypeOf(useSetValue(contextValue.countAtom)).toBeFunction() const [value, setValue] = useAtom(contextValue.countAtom) expectTypeOf(value).toExtend() @@ -191,3 +157,23 @@ test('createStoreContext preserves keyed atom and store types', () => { const selected = useSelector(readonlyStore, (state) => state.value) expectTypeOf(selected).toExtend() }) + +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _useStore(store, (state) => state.count) + + expectTypeOf(selected).toExtend() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toExtend() + expectTypeOf(setState).toEqualTypeOf['setState']>() +}) diff --git a/packages/react-store/src/_useStore.ts b/packages/react-store/src/_useStore.ts new file mode 100644 index 00000000..30fdd927 --- /dev/null +++ b/packages/react-store/src/_useStore.ts @@ -0,0 +1,43 @@ +import { useMemo } from 'react' +import { useSelector } from './useSelector' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```tsx + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +/* eslint-disable react-hooks/rules-of-hooks, @eslint-react/rules-of-hooks -- experimental API with underscore prefix */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + TSelected, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = useMemo( + () => (store.actions as StoreActionMap | undefined) ?? store.setState, + [store], + ) + + return [selected, actionsOrSetState] as any +} diff --git a/packages/react-store/src/index.ts b/packages/react-store/src/index.ts index 0716abab..4bcfb415 100644 --- a/packages/react-store/src/index.ts +++ b/packages/react-store/src/index.ts @@ -4,14 +4,9 @@ export * from './createStoreContext' export * from './useCreateAtom' export * from './useCreateStore' -export * from './useSetValue' -export * from './useStoreActions' - export * from './useValue' export * from './useSelector' export * from './useAtom' export * from './useStore' // @deprecated in favor of useSelector - -// comparators -export * from './shallow' +export * from './_useStore' diff --git a/packages/react-store/src/shallow.ts b/packages/react-store/src/shallow.ts deleted file mode 100644 index 75ff1634..00000000 --- a/packages/react-store/src/shallow.ts +++ /dev/null @@ -1,57 +0,0 @@ -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } - - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} - -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} diff --git a/packages/react-store/src/useAtom.ts b/packages/react-store/src/useAtom.ts index 2bb41d4b..862be2dc 100644 --- a/packages/react-store/src/useAtom.ts +++ b/packages/react-store/src/useAtom.ts @@ -1,4 +1,3 @@ -import { useSetValue } from './useSetValue' import { useValue } from './useValue' import type { Atom } from '@tanstack/store' import type { UseSelectorOptions } from './useSelector' @@ -19,7 +18,6 @@ export function useAtom( options?: UseSelectorOptions, ): [TValue, Atom['set']] { const value = useValue(atom, options) - const setValue = useSetValue(atom) - return [value, setValue] + return [value, atom.set] } diff --git a/packages/react-store/src/useSetValue.ts b/packages/react-store/src/useSetValue.ts deleted file mode 100644 index 87d99893..00000000 --- a/packages/react-store/src/useSetValue.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useCallback } from 'react' -import type { Atom, Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns a stable setter for a writable atom or store. - * - * Writable atoms preserve their native `set` contract, supporting both direct - * values and updater functions. Writable stores preserve their native - * `setState` contract, supporting updater functions. - * - * @example - * ```tsx - * const setCount = useSetValue(countAtom) - * setCount((prev) => prev + 1) - * ``` - * - * @example - * ```tsx - * const setState = useSetValue(appStore) - * setState((prev) => ({ ...prev, count: prev.count + 1 })) - * ``` - */ -export function useSetValue(source: Atom): Atom['set'] -export function useSetValue( - source: Store, -): Store['setState'] -export function useSetValue( - source: Atom | Store, -) { - return useCallback['set'] | Store['setState']>( - (valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { - if ('setState' in source) { - source.setState(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - if (typeof valueOrUpdater === 'function') { - source.set(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - source.set(valueOrUpdater) - } - } - }, - [source], - ) -} diff --git a/packages/react-store/src/useStoreActions.ts b/packages/react-store/src/useStoreActions.ts deleted file mode 100644 index 16de6c94..00000000 --- a/packages/react-store/src/useStoreActions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useMemo } from 'react' -import type { Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns the stable actions bag from a writable store created with actions. - * - * Use this when a component only needs to call store actions and should not - * subscribe to state changes. - * - * @example - * ```tsx - * const actions = useStoreActions(counterStore) - * actions.inc() - * ``` - */ -export function useStoreActions( - store: Store, -): TActions { - return useMemo(() => store.actions, [store]) -} diff --git a/packages/react-store/tests/index.test.tsx b/packages/react-store/tests/index.test.tsx index 9193e3c0..2beb066d 100644 --- a/packages/react-store/tests/index.test.tsx +++ b/packages/react-store/tests/index.test.tsx @@ -3,15 +3,14 @@ import { userEvent } from '@testing-library/user-event' import { describe, expect, it, test, vi } from 'vitest' import { createAtom, createStore } from '@tanstack/store' import { + _useStore, createStoreContext, shallow, useAtom, useCreateAtom, useCreateStore, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src/index' @@ -143,25 +142,6 @@ describe('atom hooks', () => { expect(getByText('Renders: 2')).toBeInTheDocument() }) - it('useSetValue updates atoms by value and updater and stays stable', () => { - const atom = createAtom(0) - const { result, rerender } = renderHook(() => useSetValue(atom)) - const setAtom = result.current - - act(() => { - result.current(1) - }) - expect(atom.get()).toBe(1) - - rerender() - expect(result.current).toBe(setAtom) - - act(() => { - result.current((prev) => prev + 1) - }) - expect(atom.get()).toBe(2) - }) - it('useAtom returns the current value and setter', () => { const atom = createAtom(0) const { result } = renderHook(() => useAtom(atom)) @@ -174,25 +154,6 @@ describe('atom hooks', () => { expect(result.current[0]).toBe(5) }) - - it('useSetValue updates stores by updater and stays stable', () => { - const store = createStore(0) - const { result, rerender } = renderHook(() => useSetValue(store)) - const setStore = result.current - - act(() => { - result.current((prev) => prev + 1) - }) - expect(store.state).toBe(1) - - rerender() - expect(result.current).toBe(setStore) - - act(() => { - result.current((prev) => prev + 1) - }) - expect(store.state).toBe(2) - }) }) describe('store contexts', () => { @@ -208,21 +169,22 @@ describe('store contexts', () => { const { countAtom: currentAtom, totalStore: currentStore } = useStoreContext() const value = useValue(currentAtom) - const setValue = useSetValue(currentAtom) const total = useSelector(currentStore, (state) => state.count) - const setTotal = useSetValue(currentStore) return (

Value: {value}

Total: {total}

- +
+ ) + } + + const { getByText } = render() + expect(getByText('Count: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument()) + }) + + it('returns selected state and setState for plain stores', async () => { + const store = createStore(0) + + function Comp() { + const [value, setState] = _useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + describe('shallow', () => { test('should return true for shallowly equal objects', () => { const objA = { a: 1, b: 'hello' } diff --git a/packages/react-store/tests/test.test-d.ts b/packages/react-store/tests/test.test-d.ts index 2aa4ab20..55b0d1b6 100644 --- a/packages/react-store/tests/test.test-d.ts +++ b/packages/react-store/tests/test.test-d.ts @@ -1,17 +1,16 @@ import { expectTypeOf, test } from 'vitest' import { createAtom, createStore } from '@tanstack/store' import { + _useStore, createStoreContext, useAtom, useCreateAtom, useCreateStore, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src' -import type { Atom, ReadonlyStore } from '@tanstack/store' +import type { Atom, ReadonlyStore, Store } from '@tanstack/store' test('useCreateAtom returns a writable atom for initial values', () => { const atom = useCreateAtom(12) @@ -46,22 +45,6 @@ test('useValue infers value from mutable and readonly atoms', () => { ).toExtend() }) -test('useSetValue preserves native atom and store setter contracts', () => { - const writableAtom = createAtom(12) - const readonlyAtom = createAtom(() => 24) - const writableStore = createStore(12) - const readonlyStore = createStore(() => 24) - - expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() - expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< - typeof writableStore.setState - >() - // @ts-expect-error readonly atoms cannot be set - useSetValue(readonlyAtom) - // @ts-expect-error readonly stores cannot be set - useSetValue(readonlyStore) -}) - test('useAtom only accepts writable atoms', () => { const writableAtom = createAtom(12) const readonlyAtom = createAtom(() => 24) @@ -80,9 +63,12 @@ test('useAtom only accepts writable atoms', () => { test('useCreateStore returns writable and readonly store types', () => { const writableStore = useCreateStore(12) - const writableStoreWithActions = useCreateStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - })) + const writableStoreWithActions = useCreateStore( + { count: 0 }, + ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + }), + ) const readonlyStore = useCreateStore(() => 24) expectTypeOf(writableStore.state).toExtend() @@ -147,25 +133,6 @@ test('useStore matches useSelector types for compatibility', () => { expectTypeOf(compatValue).toExtend() }) -test('useStoreActions infers the action bag from writable stores', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - current: () => get().count, - })) - - const actions = useStoreActions(store) - - expectTypeOf(actions.inc).toBeFunction() - expectTypeOf(actions.current()).toExtend() - - const plainStore = createStore(12) - expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() - - const readonlyStore = createStore(() => 24) - // @ts-expect-error readonly stores do not expose actions - useStoreActions(readonlyStore) -}) - test('createStoreContext preserves keyed atom and store types', () => { const countAtom = createAtom(12) const readonlySource = createStore(() => ({ value: 24 })) @@ -177,7 +144,6 @@ test('createStoreContext preserves keyed atom and store types', () => { expectTypeOf(contextValue.countAtom).toExtend>() expectTypeOf(contextValue.countAtom.set).toBeFunction() - expectTypeOf(useSetValue(contextValue.countAtom)).toBeFunction() const [value, setValue] = useAtom(contextValue.countAtom) expectTypeOf(value).toExtend() @@ -190,3 +156,23 @@ test('createStoreContext preserves keyed atom and store types', () => { const selected = useSelector(readonlyStore, (state) => state.value) expectTypeOf(selected).toExtend() }) + +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _useStore(store, (state) => state.count) + + expectTypeOf(selected).toExtend() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toExtend() + expectTypeOf(setState).toEqualTypeOf['setState']>() +}) diff --git a/packages/solid-store/src/_useStore.ts b/packages/solid-store/src/_useStore.ts new file mode 100644 index 00000000..4b2ba8b4 --- /dev/null +++ b/packages/solid-store/src/_useStore.ts @@ -0,0 +1,40 @@ +import { useSelector } from './useSelector' +import type { Accessor } from 'solid-js' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```tsx + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + Accessor, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/solid-store/src/index.tsx b/packages/solid-store/src/index.tsx index e03d889a..ea10ac37 100644 --- a/packages/solid-store/src/index.tsx +++ b/packages/solid-store/src/index.tsx @@ -1,13 +1,8 @@ export * from '@tanstack/store' -export * from './useSetValue' -export * from './useStoreActions' - export * from './useValue' export * from './useSelector' export * from './useAtom' export * from './useStore' // @deprecated in favor of useSelector - -// comparators -export * from './shallow' +export * from './_useStore' diff --git a/packages/solid-store/src/shallow.ts b/packages/solid-store/src/shallow.ts deleted file mode 100644 index 75ff1634..00000000 --- a/packages/solid-store/src/shallow.ts +++ /dev/null @@ -1,57 +0,0 @@ -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } - - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} - -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} diff --git a/packages/solid-store/src/useAtom.ts b/packages/solid-store/src/useAtom.ts index 41f82f56..7703a16c 100644 --- a/packages/solid-store/src/useAtom.ts +++ b/packages/solid-store/src/useAtom.ts @@ -1,4 +1,3 @@ -import { useSetValue } from './useSetValue' import { useValue } from './useValue' import type { Accessor } from 'solid-js' import type { Atom } from '@tanstack/store' @@ -26,7 +25,6 @@ export function useAtom( options?: UseSelectorOptions, ): [Accessor, Atom['set']] { const value = useValue(atom, options) - const setValue = useSetValue(atom) - return [value, setValue] + return [value, atom.set] } diff --git a/packages/solid-store/src/useSetValue.ts b/packages/solid-store/src/useSetValue.ts deleted file mode 100644 index 28886d68..00000000 --- a/packages/solid-store/src/useSetValue.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Atom, Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns a stable setter for a writable atom or store. - * - * Writable atoms preserve their native `set` contract. Writable stores - * preserve their native `setState` contract. - * - * @example - * ```tsx - * const setCount = useSetValue(countAtom) - * setCount((prev) => prev + 1) - * ``` - * - * @example - * ```tsx - * const setState = useSetValue(counterStore) - * setState((state) => ({ ...state, count: state.count + 1 })) - * ``` - */ -export function useSetValue(source: Atom): Atom['set'] -export function useSetValue( - source: Store, -): Store['setState'] -export function useSetValue( - source: Atom | Store, -) { - return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { - if ('setState' in source) { - source.setState(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - if (typeof valueOrUpdater === 'function') { - source.set(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - source.set(valueOrUpdater) - } - } - }) as Atom['set'] | Store['setState'] -} diff --git a/packages/solid-store/src/useStoreActions.ts b/packages/solid-store/src/useStoreActions.ts deleted file mode 100644 index 773f6ace..00000000 --- a/packages/solid-store/src/useStoreActions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns the stable actions bag from a writable store created with actions. - * - * Use this when a component only needs to call store actions and should not - * subscribe to store state. - * - * @example - * ```tsx - * const actions = useStoreActions(counterStore) - * actions.increment() - * ``` - */ -export function useStoreActions( - store: Store, -): TActions { - return store.actions -} diff --git a/packages/solid-store/tests/index.test.tsx b/packages/solid-store/tests/index.test.tsx index 08fcfff9..55c11a58 100644 --- a/packages/solid-store/tests/index.test.tsx +++ b/packages/solid-store/tests/index.test.tsx @@ -2,12 +2,11 @@ import { describe, expect, it, test, vi } from 'vitest' import { renderHook } from '@solidjs/testing-library' import { createAtom, createStore } from '@tanstack/store' import { + _useStore, shallow, useAtom, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src/index' @@ -23,17 +22,6 @@ describe('atom hooks', () => { expect(result()).toBe(1) }) - it('useSetValue updates atoms by value and updater', () => { - const atom = createAtom(0) - const { result } = renderHook(() => useSetValue(atom)) - - result(1) - expect(atom.get()).toBe(1) - - result((prev) => prev + 1) - expect(atom.get()).toBe(2) - }) - it('useAtom returns the current accessor and setter', () => { const atom = createAtom(0) const { result } = renderHook(() => useAtom(atom)) @@ -161,28 +149,6 @@ describe('store hooks', () => { expect(result()).toBe(2) }) - - it('useSetValue updates stores by updater', () => { - const store = createStore(0) - const { result } = renderHook(() => useSetValue(store)) - - result((prev) => prev + 1) - expect(store.state).toBe(1) - }) - - it('useStoreActions returns the stable actions bag', () => { - const store = createStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - })) - - const { result } = renderHook(() => useStoreActions(store)) - const actions = result - - actions.inc() - - expect(result).toBe(actions) - expect(store.state.count).toBe(1) - }) }) describe('useStore', () => { @@ -209,6 +175,36 @@ describe('useStore', () => { }) }) +describe('_useStore', () => { + it('returns selected state and actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const { result } = renderHook(() => + _useStore(store, (state) => state.count), + ) + + expect(result[0]()).toBe(0) + + result[1].inc() + + expect(result[0]()).toBe(1) + }) + + it('returns selected state and setState for plain stores', () => { + const store = createStore(0) + + const { result } = renderHook(() => _useStore(store, (state) => state)) + + expect(result[0]()).toBe(0) + + result[1]((prev) => prev + 1) + + expect(result[0]()).toBe(1) + }) +}) + describe('shallow', () => { test('should return true for shallowly equal objects', () => { const objA = { a: 1, b: 'hello' } diff --git a/packages/solid-store/tests/test.test-d.ts b/packages/solid-store/tests/test.test-d.ts index 639f8346..1e49ea64 100644 --- a/packages/solid-store/tests/test.test-d.ts +++ b/packages/solid-store/tests/test.test-d.ts @@ -1,15 +1,8 @@ import { expectTypeOf, test } from 'vitest' import { createAtom, createStore } from '@tanstack/store' -import { - useAtom, - useSelector, - useSetValue, - useStore, - useStoreActions, - useValue, -} from '../src' +import { _useStore, useAtom, useSelector, useStore, useValue } from '../src' import type { Accessor } from 'solid-js' -import type { Atom } from '@tanstack/store' +import type { Store } from '@tanstack/store' test('useSelector works with derived state', () => { const store = createStore(12) @@ -35,22 +28,6 @@ test('useValue infers value from mutable and readonly sources', () => { expectTypeOf(useValue(readonlyStore)).toEqualTypeOf>() }) -test('useSetValue preserves native setter contracts', () => { - const writableAtom = createAtom(12) - const readonlyAtom = createAtom(() => 24) - const writableStore = createStore(12) - const readonlyStore = createStore(() => 24) - - expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() - expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< - typeof writableStore.setState - >() - // @ts-expect-error readonly atoms cannot be set - useSetValue(readonlyAtom) - // @ts-expect-error readonly stores cannot be set - useSetValue(readonlyStore) -}) - test('useAtom only accepts writable atoms', () => { const writableAtom = createAtom(12) const readonlyAtom = createAtom(() => 24) @@ -72,21 +49,22 @@ test('useStore matches useSelector types for compatibility', () => { expectTypeOf(compatValue).toEqualTypeOf>() }) -test('useStoreActions infers the action bag from writable stores', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - current: () => get().count, +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) - const actions = useStoreActions(store) + const [selected, actions] = _useStore(store, (state) => state.count) + expectTypeOf(selected).toEqualTypeOf>() expectTypeOf(actions.inc).toBeFunction() - expectTypeOf(actions.current()).toExtend() +}) - const plainStore = createStore(12) - expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) - const readonlyStore = createStore(() => 24) - // @ts-expect-error readonly stores do not expose actions - useStoreActions(readonlyStore) + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf>() + expectTypeOf(setState).toEqualTypeOf['setState']>() }) diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index ff7c5459..71215a5e 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -1,3 +1,4 @@ export * from './types' export * from './atom' export * from './store' +export * from './shallow' diff --git a/packages/preact-store/src/shallow.ts b/packages/store/src/shallow.ts similarity index 100% rename from packages/preact-store/src/shallow.ts rename to packages/store/src/shallow.ts diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index b1772123..4e83b9e6 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -1,18 +1,14 @@ import { createAtom, toObserver } from './atom' import type { Atom, Observer, Subscription } from './types' -export interface StoreActionsApi { - set: (updater: (prev: T) => T) => void - get: () => T -} - export type StoreAction = (...args: Array) => any export type StoreActionMap = Record -export type StoreActionsFactory = ( - api: StoreActionsApi, -) => TActions +export type StoreActionsFactory = (store: { + setState: Store['setState'] + get: Store['get'] +}) => TActions type NonFunction = T extends (...args: Array) => any ? never : T @@ -35,11 +31,11 @@ export class Store { valueOrFn as T | ((prev?: NoInfer) => T), ) as Atom + this.setState = this.setState.bind(this) + this.get = this.get.bind(this) + if (actionsFactory) { - this.actions = actionsFactory({ - set: (updater) => this.setState(updater), - get: () => this.get(), - }) + this.actions = actionsFactory(this) } } public setState(updater: (prev: T) => T) { diff --git a/packages/store/tests/store-type-safety.test.ts b/packages/store/tests/store-type-safety.test.ts index c8568b94..e0585d8e 100644 --- a/packages/store/tests/store-type-safety.test.ts +++ b/packages/store/tests/store-type-safety.test.ts @@ -64,16 +64,18 @@ describe('Store.setState Type Safety Improvements', () => { }) test('should infer action types safely', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), + const store = createStore({ count: 0 }, ({ get, setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), current: () => get().count, })) store.actions.inc() expect(store.actions.current()).toBe(1) - // @ts-expect-error set only accepts updater functions - createStore({ count: 0 }, ({ set }) => ({ bad: () => set({ count: 1 }) })) + createStore({ count: 0 }, ({ setState }) => ({ + // @ts-expect-error setState only accepts updater functions + bad: () => setState({ count: 1 }), + })) if (typecheckOnly) { createStore({ count: 0 }, () => ({ @@ -106,8 +108,8 @@ describe('Store.setState Type Safety Improvements', () => { createStore( // @ts-expect-error function first arg with actions is not supported () => ({ count: 0 }), - ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), + ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), }), ) } diff --git a/packages/store/tests/store.test.ts b/packages/store/tests/store.test.ts index c08187b0..7c088144 100644 --- a/packages/store/tests/store.test.ts +++ b/packages/store/tests/store.test.ts @@ -39,8 +39,8 @@ describe('store', () => { }) test('supports actions on writable stores', () => { - const store = createStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) expect(store.state).toEqual({ count: 0 }) @@ -51,10 +51,10 @@ describe('store', () => { }) test('actions can read current state', () => { - const store = createStore({ count: 1 }, ({ get, set }) => ({ + const store = createStore({ count: 1 }, ({ get, setState }) => ({ addIfOdd: () => { if (get().count % 2 === 1) { - set((prev) => ({ count: prev.count + 1 })) + setState((prev) => ({ count: prev.count + 1 })) } }, })) @@ -67,8 +67,8 @@ describe('store', () => { }) test('actions bag is stable across updates', () => { - const store = createStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) const actions = store.actions diff --git a/packages/svelte-store/src/_useStore.ts b/packages/svelte-store/src/_useStore.ts new file mode 100644 index 00000000..da9caba6 --- /dev/null +++ b/packages/svelte-store/src/_useStore.ts @@ -0,0 +1,38 @@ +import { useSelector } from './useSelector.svelte.js' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector.svelte.js' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```ts + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * ``` + */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + { readonly current: TSelected }, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/svelte-store/src/index.svelte.ts b/packages/svelte-store/src/index.svelte.ts index cf23d20c..9ae7245d 100644 --- a/packages/svelte-store/src/index.svelte.ts +++ b/packages/svelte-store/src/index.svelte.ts @@ -1,242 +1,8 @@ -import type { - Atom, - ReadonlyAtom, - ReadonlyStore, - Store, - StoreActionMap, -} from '@tanstack/store' - export * from '@tanstack/store' -export interface UseSelectorOptions { - compare?: (a: TSelected, b: TSelected) => boolean -} - -function defaultCompare(a: T, b: T) { - return a === b -} - -/** - * Selects a slice of state from an atom or store and exposes it through a - * rune-friendly holder object. - * - * Read the selected value from `.current`. - * - * @example - * ```ts - * const count = useSelector(counterStore, (state) => state.count) - * console.log(count.current) - * ``` - * - * @example - * ```ts - * const doubled = useSelector(countAtom, (value) => value * 2) - * ``` - */ -export function useSelector>( - source: - | Atom - | ReadonlyAtom - | Store - | ReadonlyStore, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseSelectorOptions = {}, -): { readonly current: TSelected } { - const compare = options.compare ?? defaultCompare - let slice = $state(selector(source.get())) - - $effect(() => { - const unsub = source.subscribe((s) => { - const data = selector(s) - if (compare(slice, data)) { - return - } - slice = data - }).unsubscribe - - return unsub - }) - - return { - get current() { - return slice - }, - } -} - -/** - * Subscribes to an atom or store and returns its whole current value through a - * rune-friendly holder object. - * - * @example - * ```ts - * const count = useValue(countAtom) - * console.log(count.current) - * ``` - * - * @example - * ```ts - * const state = useValue(counterStore) - * ``` - */ -export function useValue( - source: - | Atom - | ReadonlyAtom - | Store - | ReadonlyStore, - options?: UseSelectorOptions, -): { readonly current: TValue } { - return useSelector(source, (value) => value, options) -} - -/** - * Returns a stable setter for a writable atom or store. - * - * Writable atoms preserve their native `set` contract. Writable stores - * preserve their native `setState` contract. - * - * @example - * ```ts - * const setCount = useSetValue(countAtom) - * setCount((prev) => prev + 1) - * ``` - * - * @example - * ```ts - * const setState = useSetValue(counterStore) - * setState((state) => ({ ...state, count: state.count + 1 })) - * ``` - */ -export function useSetValue(source: Atom): Atom['set'] -export function useSetValue( - source: Store, -): Store['setState'] -export function useSetValue( - source: Atom | Store, -) { - return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { - if ('setState' in source) { - source.setState(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - if (typeof valueOrUpdater === 'function') { - source.set(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - source.set(valueOrUpdater) - } - } - }) as Atom['set'] | Store['setState'] -} - -/** - * Returns the current atom holder together with a setter. - * - * Use this when a component needs to both read and update the same writable - * atom. - * - * @example - * ```ts - * const [count, setCount] = useAtom(countAtom) - * setCount((prev) => prev + 1) - * console.log(count.current) - * ``` - */ -export function useAtom( - atom: Atom, - options?: UseSelectorOptions, -): [{ readonly current: TValue }, Atom['set']] { - const value = useValue(atom, options) - const setValue = useSetValue(atom) - - return [value, setValue] -} - -/** - * Returns the stable actions bag from a writable store created with actions. - * - * Use this when a component only needs to call store actions and should not - * subscribe to store state. - * - * @example - * ```ts - * const actions = useStoreActions(counterStore) - * actions.increment() - * ``` - */ -export function useStoreActions( - store: Store, -): TActions { - return store.actions -} - -/** - * Deprecated alias for {@link useSelector}. - * - * @example - * ```ts - * const count = useStore(counterStore, (state) => state.count) - * console.log(count.current) - * ``` - * - * @deprecated Use `useSelector` instead. - */ -export const useStore = >( - source: - | Atom - | ReadonlyAtom - | Store - | ReadonlyStore, - selector: (state: NoInfer) => TSelected = (d) => d as any, - compare?: (a: TSelected, b: TSelected) => boolean, -) => useSelector(source, selector, { compare }) - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './useSelector.svelte.js' +export * from './useValue' - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector +export * from './_useStore' diff --git a/packages/svelte-store/src/useAtom.ts b/packages/svelte-store/src/useAtom.ts new file mode 100644 index 00000000..5fcbbeaa --- /dev/null +++ b/packages/svelte-store/src/useAtom.ts @@ -0,0 +1,25 @@ +import { useValue } from './useValue' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector.svelte.js' + +/** + * Returns the current atom holder together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * const [count, setCount] = useAtom(countAtom) + * setCount((prev) => prev + 1) + * console.log(count.current) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [{ readonly current: TValue }, Atom['set']] { + const value = useValue(atom, options) + + return [value, atom.set] +} diff --git a/packages/svelte-store/src/useSelector.svelte.ts b/packages/svelte-store/src/useSelector.svelte.ts new file mode 100644 index 00000000..82a72fb1 --- /dev/null +++ b/packages/svelte-store/src/useSelector.svelte.ts @@ -0,0 +1,57 @@ +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and exposes it through a + * rune-friendly holder object. + * + * Read the selected value from `.current`. + * + * @example + * ```ts + * const count = useSelector(counterStore, (state) => state.count) + * console.log(count.current) + * ``` + * + * @example + * ```ts + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector>( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + selector: (state: NoInfer) => TSelected = (d) => d as any, + options: UseSelectorOptions = {}, +): { readonly current: TSelected } { + const compare = options.compare ?? defaultCompare + let slice = $state(selector(source.get())) + + $effect(() => { + const unsub = source.subscribe((s) => { + const data = selector(s) + if (compare(slice, data)) { + return + } + slice = data + }).unsubscribe + + return unsub + }) + + return { + get current() { + return slice + }, + } +} diff --git a/packages/svelte-store/src/useStore.ts b/packages/svelte-store/src/useStore.ts new file mode 100644 index 00000000..b3a5642b --- /dev/null +++ b/packages/svelte-store/src/useStore.ts @@ -0,0 +1,23 @@ +import { useSelector } from './useSelector.svelte.js' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```ts + * const count = useStore(counterStore, (state) => state.count) + * console.log(count.current) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = >( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + selector: (state: NoInfer) => TSelected = (d) => d as any, + compare?: (a: TSelected, b: TSelected) => boolean, +) => useSelector(source, selector, { compare }) diff --git a/packages/svelte-store/src/useValue.ts b/packages/svelte-store/src/useValue.ts new file mode 100644 index 00000000..95765c4a --- /dev/null +++ b/packages/svelte-store/src/useValue.ts @@ -0,0 +1,29 @@ +import { useSelector } from './useSelector.svelte.js' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector.svelte.js' + +/** + * Subscribes to an atom or store and returns its whole current value through a + * rune-friendly holder object. + * + * @example + * ```ts + * const count = useValue(countAtom) + * console.log(count.current) + * ``` + * + * @example + * ```ts + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): { readonly current: TValue } { + return useSelector(source, (value) => value, options) +} diff --git a/packages/svelte-store/tests/index.test.ts b/packages/svelte-store/tests/index.test.ts index f4bb1b6a..158fc0ef 100644 --- a/packages/svelte-store/tests/index.test.ts +++ b/packages/svelte-store/tests/index.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it, test } from 'vitest' import { render, waitFor } from '@testing-library/svelte' import { userEvent } from '@testing-library/user-event' -import { createStore } from '@tanstack/store' -import { shallow, useStoreActions } from '../src/index.svelte.js' +import { shallow } from '../src/index.svelte.js' import TestBaseStore from './BaseStore.test.svelte' import TestRerender from './Render.test.svelte' import TestValue from './Value.test.svelte' @@ -39,17 +38,6 @@ describe('useSelector', () => { await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) }) - - it('useStoreActions returns the stable actions bag', () => { - const store = createStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - })) - - const actions = useStoreActions(store) - actions.inc() - - expect(store.state.count).toBe(1) - }) }) describe('shallow', () => { diff --git a/packages/svelte-store/tests/test.test-d.ts b/packages/svelte-store/tests/test.test-d.ts index be1748b3..d26acb20 100644 --- a/packages/svelte-store/tests/test.test-d.ts +++ b/packages/svelte-store/tests/test.test-d.ts @@ -1,14 +1,13 @@ import { expectTypeOf, test } from 'vitest' import { createAtom, createStore } from '@tanstack/store' import { + _useStore, useAtom, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src/index.svelte.js' -import type { Atom } from '@tanstack/store' +import type { Store } from '@tanstack/store' test('useSelector works with derived state', () => { const store = createStore(12) @@ -42,22 +41,6 @@ test('useValue infers value from mutable and readonly sources', () => { }>() }) -test('useSetValue preserves native setter contracts', () => { - const writableAtom = createAtom(12) - const readonlyAtom = createAtom(() => 24) - const writableStore = createStore(12) - const readonlyStore = createStore(() => 24) - - expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() - expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< - typeof writableStore.setState - >() - // @ts-expect-error readonly atoms cannot be set - useSetValue(readonlyAtom) - // @ts-expect-error readonly stores cannot be set - useSetValue(readonlyStore) -}) - test('useAtom only accepts writable atoms', () => { const writableAtom = createAtom(12) const readonlyAtom = createAtom(() => 24) @@ -79,21 +62,23 @@ test('useStore matches useSelector types for compatibility', () => { expectTypeOf(compatValue).toEqualTypeOf<{ readonly current: number }>() }) -test('useStoreActions infers the action bag from writable stores', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - current: () => get().count, +test('_useStore returns selected state and second tuple element for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) - const actions = useStoreActions(store) + const result = _useStore(store, (state) => state.count) - expectTypeOf(actions.inc).toBeFunction() - expectTypeOf(actions.current()).toExtend() + expectTypeOf(result[0]).toEqualTypeOf<{ readonly current: number }>() + // The second element should be the actions bag + expectTypeOf(result).toBeArray() +}) - const plainStore = createStore(12) - expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) - const readonlyStore = createStore(() => 24) - // @ts-expect-error readonly stores do not expose actions - useStoreActions(readonlyStore) + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf<{ readonly current: number }>() + expectTypeOf(setState).toEqualTypeOf['setState']>() }) diff --git a/packages/vue-store/src/_useStore.ts b/packages/vue-store/src/_useStore.ts new file mode 100644 index 00000000..f7401fb8 --- /dev/null +++ b/packages/vue-store/src/_useStore.ts @@ -0,0 +1,41 @@ +import { useSelector } from './useSelector' +import type { Ref } from 'vue-demi' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```ts + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * console.log(cats.value) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + Readonly>, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/vue-store/src/index.ts b/packages/vue-store/src/index.ts index e03d889a..ea10ac37 100644 --- a/packages/vue-store/src/index.ts +++ b/packages/vue-store/src/index.ts @@ -1,13 +1,8 @@ export * from '@tanstack/store' -export * from './useSetValue' -export * from './useStoreActions' - export * from './useValue' export * from './useSelector' export * from './useAtom' export * from './useStore' // @deprecated in favor of useSelector - -// comparators -export * from './shallow' +export * from './_useStore' diff --git a/packages/vue-store/src/shallow.ts b/packages/vue-store/src/shallow.ts deleted file mode 100644 index 75ff1634..00000000 --- a/packages/vue-store/src/shallow.ts +++ /dev/null @@ -1,57 +0,0 @@ -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } - - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} - -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} diff --git a/packages/vue-store/src/useAtom.ts b/packages/vue-store/src/useAtom.ts index e49ef39f..673441a3 100644 --- a/packages/vue-store/src/useAtom.ts +++ b/packages/vue-store/src/useAtom.ts @@ -1,4 +1,3 @@ -import { useSetValue } from './useSetValue' import { useValue } from './useValue' import type { Ref } from 'vue-demi' import type { Atom } from '@tanstack/store' @@ -23,7 +22,6 @@ export function useAtom( options?: UseSelectorOptions, ): [Readonly>, Atom['set']] { const value = useValue(atom, options) - const setValue = useSetValue(atom) - return [value, setValue] + return [value, atom.set] } diff --git a/packages/vue-store/src/useSetValue.ts b/packages/vue-store/src/useSetValue.ts deleted file mode 100644 index 9cf8de17..00000000 --- a/packages/vue-store/src/useSetValue.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Atom, Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns a stable setter for a writable atom or store. - * - * Writable atoms preserve their native `set` contract. Writable stores - * preserve their native `setState` contract. - * - * @example - * ```ts - * const setCount = useSetValue(countAtom) - * setCount((prev) => prev + 1) - * ``` - * - * @example - * ```ts - * const setState = useSetValue(counterStore) - * setState((state) => ({ ...state, count: state.count + 1 })) - * ``` - */ -export function useSetValue(source: Atom): Atom['set'] -export function useSetValue( - source: Store, -): Store['setState'] -export function useSetValue( - source: Atom | Store, -) { - return ((valueOrUpdater: TValue | ((prevVal: TValue) => TValue)) => { - if ('setState' in source) { - source.setState(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - if (typeof valueOrUpdater === 'function') { - source.set(valueOrUpdater as (prevVal: TValue) => TValue) - } else { - source.set(valueOrUpdater) - } - } - }) as Atom['set'] | Store['setState'] -} diff --git a/packages/vue-store/src/useStoreActions.ts b/packages/vue-store/src/useStoreActions.ts deleted file mode 100644 index a6ab7fc2..00000000 --- a/packages/vue-store/src/useStoreActions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Store, StoreActionMap } from '@tanstack/store' - -/** - * Returns the stable actions bag from a writable store created with actions. - * - * Use this when a component only needs to call store actions and should not - * subscribe to store state. - * - * @example - * ```ts - * const actions = useStoreActions(counterStore) - * actions.increment() - * ``` - */ -export function useStoreActions( - store: Store, -): TActions { - return store.actions -} diff --git a/packages/vue-store/tests/index.test.tsx b/packages/vue-store/tests/index.test.tsx index ab5ac048..c5d36a97 100644 --- a/packages/vue-store/tests/index.test.tsx +++ b/packages/vue-store/tests/index.test.tsx @@ -4,12 +4,11 @@ import { render, waitFor } from '@testing-library/vue' import { createAtom, createStore } from '@tanstack/store' import { userEvent } from '@testing-library/user-event' import { + _useStore, shallow, useAtom, useSelector, - useSetValue, useStore, - useStoreActions, useValue, } from '../src/index' @@ -272,46 +271,6 @@ describe('store hooks', () => { await waitFor(() => expect(getByText('Derived: 2')).toBeInTheDocument()) }) - - it('useSetValue updates stores by updater', async () => { - const store = createStore(0) - - const Comp = defineComponent(() => { - const setValue = useSetValue(store) - - return () => - h('button', { onClick: () => setValue((prev) => prev + 1) }, 'Update') - }) - - const { getByText } = render(Comp) - - await user.click(getByText('Update')) - - expect(store.state).toBe(1) - }) - - it('useStoreActions returns the stable actions bag without subscribing to state', async () => { - const store = createStore({ count: 0 }, ({ set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - })) - - const Comp = defineComponent(() => { - const actions = useStoreActions(store) - - return () => - h( - 'button', - { onClick: () => actions.inc() }, - `Count: ${store.state.count}`, - ) - }) - - const { getByText } = render(Comp) - - await user.click(getByText('Count: 0')) - - expect(store.state.count).toBe(1) - }) }) describe('useStore', () => { @@ -370,6 +329,52 @@ describe('useStore', () => { }) }) +describe('_useStore', () => { + it('returns selected state and actions for stores with actions', async () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const Comp = defineComponent(() => { + const [count, { inc }] = _useStore(store, (state) => state.count) + + return () => + h('div', [ + h('p', `Count: ${count.value}`), + h('button', { onClick: () => inc() }, 'Inc'), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Count: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument()) + }) + + it('returns selected state and setState for plain stores', async () => { + const store = createStore(0) + + const Comp = defineComponent(() => { + const [value, setState] = _useStore(store, (state) => state) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h('button', { onClick: () => setState((prev) => prev + 1) }, 'Inc'), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + describe('shallow', () => { test('should return true for shallowly equal objects', () => { const objA = { a: 1, b: 'hello' } diff --git a/packages/vue-store/tests/test.test-d.ts b/packages/vue-store/tests/test.test-d.ts index 8e36f282..f224628f 100644 --- a/packages/vue-store/tests/test.test-d.ts +++ b/packages/vue-store/tests/test.test-d.ts @@ -1,15 +1,8 @@ import { expectTypeOf, test } from 'vitest' import { createAtom, createStore } from '@tanstack/store' -import { - useAtom, - useSelector, - useSetValue, - useStore, - useStoreActions, - useValue, -} from '../src' +import { _useStore, useAtom, useSelector, useStore, useValue } from '../src' import type { Ref } from 'vue-demi' -import type { Atom } from '@tanstack/store' +import type { Store } from '@tanstack/store' test('useSelector works with derived state', () => { const store = createStore(12) @@ -35,22 +28,6 @@ test('useValue infers value from mutable and readonly sources', () => { expectTypeOf(useValue(readonlyStore)).toEqualTypeOf>>() }) -test('useSetValue preserves native setter contracts', () => { - const writableAtom = createAtom(12) - const readonlyAtom = createAtom(() => 24) - const writableStore = createStore(12) - const readonlyStore = createStore(() => 24) - - expectTypeOf(useSetValue(writableAtom)).toEqualTypeOf['set']>() - expectTypeOf(useSetValue(writableStore)).toEqualTypeOf< - typeof writableStore.setState - >() - // @ts-expect-error readonly atoms cannot be set - useSetValue(readonlyAtom) - // @ts-expect-error readonly stores cannot be set - useSetValue(readonlyStore) -}) - test('useAtom only accepts writable atoms', () => { const writableAtom = createAtom(12) const readonlyAtom = createAtom(() => 24) @@ -72,21 +49,22 @@ test('useStore matches useSelector types for compatibility', () => { expectTypeOf(compatValue).toEqualTypeOf>>() }) -test('useStoreActions infers the action bag from writable stores', () => { - const store = createStore({ count: 0 }, ({ get, set }) => ({ - inc: () => set((prev) => ({ count: prev.count + 1 })), - current: () => get().count, +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), })) - const actions = useStoreActions(store) + const [selected, actions] = _useStore(store, (state) => state.count) + expectTypeOf(selected).toEqualTypeOf>>() expectTypeOf(actions.inc).toBeFunction() - expectTypeOf(actions.current()).toExtend() +}) - const plainStore = createStore(12) - expectTypeOf(useStoreActions(plainStore)).toEqualTypeOf() +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) - const readonlyStore = createStore(() => 24) - // @ts-expect-error readonly stores do not expose actions - useStoreActions(readonlyStore) + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf>>() + expectTypeOf(setState).toEqualTypeOf['setState']>() }) From 255321ff76480f0ea940c55d1f0135a88c12e471 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:07:36 +0000 Subject: [PATCH 5/5] ci: apply automated fixes and generate docs --- .../reference/functions/createStoreContext.md | 94 +++++++++++++++ .../angular/reference/functions/injectAtom.md | 17 +-- .../reference/functions/injectSelector.md | 4 +- .../reference/functions/injectSetValue.md | 108 ------------------ .../reference/functions/injectStore-1.md | 55 +++++++++ .../reference/functions/injectStore.md | 44 ++++--- .../reference/functions/injectStoreActions.md | 47 -------- .../reference/functions/injectValue.md | 2 +- .../angular/reference/functions/shallow.md | 32 ------ docs/framework/angular/reference/index.md | 12 +- .../interfaces/InjectSelectorOptions.md | 6 +- .../interfaces/WritableAtomSignal.md | 54 +++++++++ .../reference/type-aliases/SelectionSource.md | 62 ++++++++++ .../reference/functions/createStoreContext.md | 2 +- .../preact/reference/functions/shallow.md | 32 ------ .../preact/reference/functions/useAtom.md | 2 +- .../preact/reference/functions/useSetValue.md | 104 ----------------- .../preact/reference/functions/useStore-1.md | 61 ++++++++++ .../preact/reference/functions/useStore.md | 59 +++++----- .../reference/functions/useStoreActions.md | 44 ------- docs/framework/preact/reference/index.md | 6 +- .../reference/functions/createStoreContext.md | 2 +- .../react/reference/functions/shallow.md | 32 ------ .../react/reference/functions/useAtom.md | 2 +- .../react/reference/functions/useSetValue.md | 106 ----------------- .../react/reference/functions/useStore-1.md | 61 ++++++++++ .../react/reference/functions/useStore.md | 59 +++++----- .../reference/functions/useStoreActions.md | 44 ------- docs/framework/react/reference/index.md | 6 +- .../solid/reference/functions/shallow.md | 32 ------ .../solid/reference/functions/useAtom.md | 2 +- .../solid/reference/functions/useSetValue.md | 104 ----------------- .../solid/reference/functions/useStore-1.md | 61 ++++++++++ .../solid/reference/functions/useStore.md | 59 +++++----- .../reference/functions/useStoreActions.md | 44 ------- docs/framework/solid/reference/index.md | 6 +- .../svelte/reference/functions/shallow.md | 32 ------ .../svelte/reference/functions/useAtom.md | 2 +- .../svelte/reference/functions/useSelector.md | 2 +- .../svelte/reference/functions/useSetValue.md | 104 ----------------- .../svelte/reference/functions/useStore-1.md | 62 ++++++++++ .../svelte/reference/functions/useStore.md | 55 +++++---- .../reference/functions/useStoreActions.md | 44 ------- .../svelte/reference/functions/useValue.md | 2 +- docs/framework/svelte/reference/index.md | 6 +- .../interfaces/UseSelectorOptions.md | 4 +- .../vue/reference/functions/useAtom.md | 2 +- .../vue/reference/functions/useSetValue.md | 104 ----------------- .../vue/reference/functions/useStore-1.md | 61 ++++++++++ .../vue/reference/functions/useStore.md | 60 +++++----- .../reference/functions/useStoreActions.md | 44 ------- docs/framework/vue/reference/index.md | 6 +- docs/reference/classes/ReadonlyStore.md | 12 +- docs/reference/classes/Store.md | 18 +-- docs/reference/functions/createStore.md | 6 +- .../vue => }/reference/functions/shallow.md | 2 +- docs/reference/index.md | 2 +- docs/reference/interfaces/StoreActionsApi.md | 48 -------- docs/reference/type-aliases/StoreAction.md | 2 +- docs/reference/type-aliases/StoreActionMap.md | 2 +- .../type-aliases/StoreActionsFactory.md | 14 ++- 61 files changed, 830 insertions(+), 1333 deletions(-) create mode 100644 docs/framework/angular/reference/functions/createStoreContext.md delete mode 100644 docs/framework/angular/reference/functions/injectSetValue.md create mode 100644 docs/framework/angular/reference/functions/injectStore-1.md delete mode 100644 docs/framework/angular/reference/functions/injectStoreActions.md delete mode 100644 docs/framework/angular/reference/functions/shallow.md create mode 100644 docs/framework/angular/reference/interfaces/WritableAtomSignal.md create mode 100644 docs/framework/angular/reference/type-aliases/SelectionSource.md delete mode 100644 docs/framework/preact/reference/functions/shallow.md delete mode 100644 docs/framework/preact/reference/functions/useSetValue.md create mode 100644 docs/framework/preact/reference/functions/useStore-1.md delete mode 100644 docs/framework/preact/reference/functions/useStoreActions.md delete mode 100644 docs/framework/react/reference/functions/shallow.md delete mode 100644 docs/framework/react/reference/functions/useSetValue.md create mode 100644 docs/framework/react/reference/functions/useStore-1.md delete mode 100644 docs/framework/react/reference/functions/useStoreActions.md delete mode 100644 docs/framework/solid/reference/functions/shallow.md delete mode 100644 docs/framework/solid/reference/functions/useSetValue.md create mode 100644 docs/framework/solid/reference/functions/useStore-1.md delete mode 100644 docs/framework/solid/reference/functions/useStoreActions.md delete mode 100644 docs/framework/svelte/reference/functions/shallow.md delete mode 100644 docs/framework/svelte/reference/functions/useSetValue.md create mode 100644 docs/framework/svelte/reference/functions/useStore-1.md delete mode 100644 docs/framework/svelte/reference/functions/useStoreActions.md delete mode 100644 docs/framework/vue/reference/functions/useSetValue.md create mode 100644 docs/framework/vue/reference/functions/useStore-1.md delete mode 100644 docs/framework/vue/reference/functions/useStoreActions.md rename docs/{framework/vue => }/reference/functions/shallow.md (63%) delete mode 100644 docs/reference/interfaces/StoreActionsApi.md diff --git a/docs/framework/angular/reference/functions/createStoreContext.md b/docs/framework/angular/reference/functions/createStoreContext.md new file mode 100644 index 00000000..05ac169b --- /dev/null +++ b/docs/framework/angular/reference/functions/createStoreContext.md @@ -0,0 +1,94 @@ +--- +id: createStoreContext +title: createStoreContext +--- + +# Function: createStoreContext() + +```ts +function createStoreContext(): object; +``` + +Defined in: [packages/angular-store/src/createStoreContext.ts:48](https://github.com/TanStack/store/blob/main/packages/angular-store/src/createStoreContext.ts#L48) + +Creates a typed Angular dependency-injection context for sharing a bundle of +atoms and stores with a component subtree. + +The returned `provideStoreContext` function accepts a factory that creates the +context value. Using a factory (rather than a static value) ensures each +component instance — and each SSR request — receives its own state, avoiding +cross-request pollution. + +Consumers call `injectStoreContext()` inside an injection context (typically a +constructor or field initializer) to retrieve the contextual atoms and stores, +then compose them with existing hooks like [injectSelector](injectSelector.md), +[injectValue](injectValue.md), and [injectAtom](injectAtom.md). + +## Type Parameters + +### TValue + +`TValue` *extends* `object` + +## Returns + +`object` + +### injectStoreContext() + +```ts +injectStoreContext: () => TValue; +``` + +#### Returns + +`TValue` + +### provideStoreContext() + +```ts +provideStoreContext: (factory) => Provider; +``` + +#### Parameters + +##### factory + +() => `TValue` + +#### Returns + +`Provider` + +## Example + +```ts +const { provideStoreContext, injectStoreContext } = createStoreContext<{ + countAtom: Atom + totalsStore: Store<{ count: number }> +}>() + +// Parent component provides the context +@Component({ + providers: [ + provideStoreContext(() => ({ + countAtom: createAtom(0), + totalsStore: new Store({ count: 0 }), + })), + ], + template: ``, +}) +class ParentComponent {} + +// Child component consumes the context +@Component({ template: `{{ count() }}` }) +class ChildComponent { + private ctx = injectStoreContext() + count = injectValue(this.ctx.countAtom) +} +``` + +## Throws + +When `injectStoreContext()` is called without a matching + `provideStoreContext()` in a parent component's providers. diff --git a/docs/framework/angular/reference/functions/injectAtom.md b/docs/framework/angular/reference/functions/injectAtom.md index f0346fe4..8d9bd6ef 100644 --- a/docs/framework/angular/reference/functions/injectAtom.md +++ b/docs/framework/angular/reference/functions/injectAtom.md @@ -6,12 +6,13 @@ title: injectAtom # Function: injectAtom() ```ts -function injectAtom(atom, options?): [Signal, (fn) => void & (value) => void]; +function injectAtom(atom, options?): WritableAtomSignal; ``` -Defined in: [packages/angular-store/src/index.ts:197](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L197) +Defined in: [packages/angular-store/src/injectAtom.ts:44](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L44) -Returns the current atom signal together with a setter. +Returns a [WritableAtomSignal](../interfaces/WritableAtomSignal.md) that reads the current atom value when +called and exposes a `.set` method for updates. Use this when a component needs to both read and update the same writable atom. @@ -34,12 +35,14 @@ atom. ## Returns -\[`Signal`\<`TValue`\>, (`fn`) => `void` & (`value`) => `void`\] +[`WritableAtomSignal`](../interfaces/WritableAtomSignal.md)\<`TValue`\> ## Example ```ts -readonly atomTuple = injectAtom(countAtom) -readonly count = this.atomTuple[0] -readonly setCount = this.atomTuple[1] +readonly count = injectAtom(countAtom) + +increment() { + this.count.set((prev) => prev + 1) +} ``` diff --git a/docs/framework/angular/reference/functions/injectSelector.md b/docs/framework/angular/reference/functions/injectSelector.md index f3c708f3..a92e9bc2 100644 --- a/docs/framework/angular/reference/functions/injectSelector.md +++ b/docs/framework/angular/reference/functions/injectSelector.md @@ -12,7 +12,7 @@ function injectSelector( options?): Signal; ``` -Defined in: [packages/angular-store/src/index.ts:107](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L107) +Defined in: [packages/angular-store/src/injectSelector.ts:93](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L93) Selects a slice of state from an atom or store and returns it as an Angular signal. @@ -33,7 +33,7 @@ This is the primary Angular read hook for TanStack Store. ### source -`SelectionSource`\<`TState`\> +[`SelectionSource`](../type-aliases/SelectionSource.md)\<`TState`\> ### selector diff --git a/docs/framework/angular/reference/functions/injectSetValue.md b/docs/framework/angular/reference/functions/injectSetValue.md deleted file mode 100644 index 4667fdd8..00000000 --- a/docs/framework/angular/reference/functions/injectSetValue.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -id: injectSetValue -title: injectSetValue ---- - -# Function: injectSetValue() - -## Call Signature - -```ts -function injectSetValue(source): (fn) => void & (value) => void; -``` - -Defined in: [packages/angular-store/src/index.ts:162](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L162) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -### Parameters - -#### source - -`Atom`\<`TValue`\> - -### Returns - -(`fn`) => `void` & (`value`) => `void` - -### Examples - -```ts -readonly setCount = injectSetValue(countAtom) - -increment() { - this.setCount((prev) => prev + 1) -} -``` - -```ts -readonly setState = injectSetValue(counterStore) -``` - -## Call Signature - -```ts -function injectSetValue(source): (updater) => void; -``` - -Defined in: [packages/angular-store/src/index.ts:165](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L165) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -#### TActions - -`TActions` *extends* `StoreActionMap` - -### Parameters - -#### source - -`Store`\<`TValue`, `TActions`\> - -### Returns - -```ts -(updater): void; -``` - -#### Parameters - -##### updater - -(`prev`) => `TValue` - -#### Returns - -`void` - -### Examples - -```ts -readonly setCount = injectSetValue(countAtom) - -increment() { - this.setCount((prev) => prev + 1) -} -``` - -```ts -readonly setState = injectSetValue(counterStore) -``` diff --git a/docs/framework/angular/reference/functions/injectStore-1.md b/docs/framework/angular/reference/functions/injectStore-1.md new file mode 100644 index 00000000..6e89cddc --- /dev/null +++ b/docs/framework/angular/reference/functions/injectStore-1.md @@ -0,0 +1,55 @@ +--- +id: injectStore +title: injectStore +--- + +# ~~Function: injectStore()~~ + +```ts +function injectStore( + store, + selector?, +options?): Signal; +``` + +Defined in: [packages/angular-store/src/injectStore.ts:20](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectStore.ts#L20) + +Deprecated alias for [injectSelector](injectSelector.md). + +## Type Parameters + +### TState + +`TState` + +### TSelected + +`TSelected` = `NoInfer`\<`TState`\> + +## Parameters + +### store + +[`SelectionSource`](../type-aliases/SelectionSource.md)\<`TState`\> + +### selector? + +(`state`) => `TSelected` + +### options? + +`CompatibilityInjectStoreOptions`\<`TSelected`\> + +## Returns + +`Signal`\<`TSelected`\> + +## Example + +```ts +readonly count = injectStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `injectSelector` instead. diff --git a/docs/framework/angular/reference/functions/injectStore.md b/docs/framework/angular/reference/functions/injectStore.md index 78fbb452..b88a398b 100644 --- a/docs/framework/angular/reference/functions/injectStore.md +++ b/docs/framework/angular/reference/functions/injectStore.md @@ -1,20 +1,24 @@ --- -id: injectStore -title: injectStore +id: _injectStore +title: _injectStore --- -# ~~Function: injectStore()~~ +# Function: \_injectStore() ```ts -function injectStore( +function _injectStore( store, - selector?, -options?): Signal; + selector, + options?): [Signal, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [packages/angular-store/src/index.ts:238](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L238) +Defined in: [packages/angular-store/src/\_injectStore.ts:24](https://github.com/TanStack/store/blob/main/packages/angular-store/src/_injectStore.ts#L24) -Deprecated alias for [injectSelector](injectSelector.md). +Experimental combined read+write injection function for stores, mirroring +injectAtom's pattern. + +Returns `[signal, actions]` when the store has an actions factory, or +`[signal, setState]` for plain stores. ## Type Parameters @@ -22,6 +26,10 @@ Deprecated alias for [injectSelector](injectSelector.md). `TState` +### TActions + +`TActions` *extends* `StoreActionMap` + ### TSelected `TSelected` = `NoInfer`\<`TState`\> @@ -30,26 +38,28 @@ Deprecated alias for [injectSelector](injectSelector.md). ### store -`SelectionSource`\<`TState`\> +`Store`\<`TState`, `TActions`\> -### selector? +### selector (`state`) => `TSelected` ### options? -`CompatibilityInjectStoreOptions`\<`TSelected`\> +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TSelected`\> ## Returns -`Signal`\<`TSelected`\> +\[`Signal`\<`TSelected`\>, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] ## Example ```ts -readonly count = injectStore(counterStore, (state) => state.count) -``` - -## Deprecated +// Store with actions +readonly result = _injectStore(petStore, (s) => s.cats) +// result[0] is Signal, result[1] is actions -Use `injectSelector` instead. +// Store without actions +readonly result = _injectStore(plainStore, (s) => s) +// result[0] is Signal, result[1] is setState +``` diff --git a/docs/framework/angular/reference/functions/injectStoreActions.md b/docs/framework/angular/reference/functions/injectStoreActions.md deleted file mode 100644 index 4674e385..00000000 --- a/docs/framework/angular/reference/functions/injectStoreActions.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: injectStoreActions -title: injectStoreActions ---- - -# Function: injectStoreActions() - -```ts -function injectStoreActions(store): TActions; -``` - -Defined in: [packages/angular-store/src/index.ts:222](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L222) - -Returns the stable actions bag from a writable store created with actions. - -Use this when a component only needs to call store actions and should not -subscribe to store state. - -## Type Parameters - -### TValue - -`TValue` - -### TActions - -`TActions` *extends* `StoreActionMap` - -## Parameters - -### store - -`Store`\<`TValue`, `TActions`\> - -## Returns - -`TActions` - -## Example - -```ts -readonly actions = injectStoreActions(counterStore) - -increment() { - this.actions.increment() -} -``` diff --git a/docs/framework/angular/reference/functions/injectValue.md b/docs/framework/angular/reference/functions/injectValue.md index d6e26c26..834dc44e 100644 --- a/docs/framework/angular/reference/functions/injectValue.md +++ b/docs/framework/angular/reference/functions/injectValue.md @@ -9,7 +9,7 @@ title: injectValue function injectValue(source, options?): Signal; ``` -Defined in: [packages/angular-store/src/index.ts:131](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L131) +Defined in: [packages/angular-store/src/injectValue.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectValue.ts#L21) Returns the current value signal for an atom or store. diff --git a/docs/framework/angular/reference/functions/shallow.md b/docs/framework/angular/reference/functions/shallow.md deleted file mode 100644 index de4d5f96..00000000 --- a/docs/framework/angular/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [packages/angular-store/src/index.ts:258](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L258) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md index e162c8cd..a836ecd2 100644 --- a/docs/framework/angular/reference/index.md +++ b/docs/framework/angular/reference/index.md @@ -8,13 +8,17 @@ title: "@tanstack/angular-store" ## Interfaces - [InjectSelectorOptions](interfaces/InjectSelectorOptions.md) +- [WritableAtomSignal](interfaces/WritableAtomSignal.md) + +## Type Aliases + +- [SelectionSource](type-aliases/SelectionSource.md) ## Functions +- [\_injectStore](functions/injectStore.md) +- [createStoreContext](functions/createStoreContext.md) - [injectAtom](functions/injectAtom.md) - [injectSelector](functions/injectSelector.md) -- [injectSetValue](functions/injectSetValue.md) -- [~~injectStore~~](functions/injectStore.md) -- [injectStoreActions](functions/injectStoreActions.md) +- [~~injectStore~~](functions/injectStore-1.md) - [injectValue](functions/injectValue.md) -- [shallow](functions/shallow.md) diff --git a/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md b/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md index e2e5ee5e..993b6db4 100644 --- a/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md +++ b/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md @@ -5,7 +5,7 @@ title: InjectSelectorOptions # Interface: InjectSelectorOptions\ -Defined in: [packages/angular-store/src/index.ts:20](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L20) +Defined in: [packages/angular-store/src/injectSelector.ts:11](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L11) ## Extends @@ -25,7 +25,7 @@ Defined in: [packages/angular-store/src/index.ts:20](https://github.com/TanStack optional compare: (a, b) => boolean; ``` -Defined in: [packages/angular-store/src/index.ts:24](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L24) +Defined in: [packages/angular-store/src/injectSelector.ts:15](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L15) #### Parameters @@ -67,4 +67,4 @@ Omit.debugName optional injector: Injector; ``` -Defined in: [packages/angular-store/src/index.ts:25](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L25) +Defined in: [packages/angular-store/src/injectSelector.ts:16](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L16) diff --git a/docs/framework/angular/reference/interfaces/WritableAtomSignal.md b/docs/framework/angular/reference/interfaces/WritableAtomSignal.md new file mode 100644 index 00000000..ce3ea69a --- /dev/null +++ b/docs/framework/angular/reference/interfaces/WritableAtomSignal.md @@ -0,0 +1,54 @@ +--- +id: WritableAtomSignal +title: WritableAtomSignal +--- + +# Interface: WritableAtomSignal()\ + +Defined in: [packages/angular-store/src/injectAtom.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L21) + +A callable signal that reads the current atom value when invoked and +exposes a `.set` method matching the atom's native setter contract. + +This is the Angular-idiomatic return type for [injectAtom](../functions/injectAtom.md). It can +be used as a class property and called directly in templates. + +## Example + +```ts +readonly count = injectAtom(countAtom) + +// read in template: {{ count() }} +// write in class: this.count.set(5) +// this.count.set(prev => prev + 1) +``` + +## Type Parameters + +### T + +`T` + +```ts +WritableAtomSignal(): T; +``` + +Defined in: [packages/angular-store/src/injectAtom.ts:23](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L23) + +Read the current value. + +## Returns + +`T` + +## Properties + +### set + +```ts +set: (fn) => void & (value) => void; +``` + +Defined in: [packages/angular-store/src/injectAtom.ts:25](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L25) + +Set the atom value (accepts a direct value or an updater function). diff --git a/docs/framework/angular/reference/type-aliases/SelectionSource.md b/docs/framework/angular/reference/type-aliases/SelectionSource.md new file mode 100644 index 00000000..03444a60 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/SelectionSource.md @@ -0,0 +1,62 @@ +--- +id: SelectionSource +title: SelectionSource +--- + +# Type Alias: SelectionSource\ + +```ts +type SelectionSource = object; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:19](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L19) + +## Type Parameters + +### T + +`T` + +## Properties + +### get() + +```ts +get: () => T; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:20](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L20) + +#### Returns + +`T` + +*** + +### subscribe() + +```ts +subscribe: (listener) => object; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L21) + +#### Parameters + +##### listener + +(`value`) => `void` + +#### Returns + +`object` + +##### unsubscribe() + +```ts +unsubscribe: () => void; +``` + +###### Returns + +`void` diff --git a/docs/framework/preact/reference/functions/createStoreContext.md b/docs/framework/preact/reference/functions/createStoreContext.md index 096e5493..bef5f673 100644 --- a/docs/framework/preact/reference/functions/createStoreContext.md +++ b/docs/framework/preact/reference/functions/createStoreContext.md @@ -17,7 +17,7 @@ a subtree. The returned `StoreProvider` only transports the provided object through Preact context. Consumers destructure the contextual atoms and stores, then compose them with the existing hooks like [useSelector](useSelector.md), -[useValue](useValue.md), [useSetValue](useSetValue.md), and [useAtom](useAtom.md). +[useValue](useValue.md), useSetValue, and [useAtom](useAtom.md). The object shape is preserved exactly, so keyed atoms and stores remain fully typed when read back with `useStoreContext()`. diff --git a/docs/framework/preact/reference/functions/shallow.md b/docs/framework/preact/reference/functions/shallow.md deleted file mode 100644 index d4fcbf03..00000000 --- a/docs/framework/preact/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [preact-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/preact-store/src/shallow.ts#L1) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/preact/reference/functions/useAtom.md b/docs/framework/preact/reference/functions/useAtom.md index fbf348c6..9a0daab4 100644 --- a/docs/framework/preact/reference/functions/useAtom.md +++ b/docs/framework/preact/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [TValue, (fn) => void & (value) => void]; ``` -Defined in: [preact-store/src/useAtom.ts:23](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useAtom.ts#L23) +Defined in: [preact-store/src/useAtom.ts:22](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useAtom.ts#L22) Returns the current atom value together with a stable setter. diff --git a/docs/framework/preact/reference/functions/useSetValue.md b/docs/framework/preact/reference/functions/useSetValue.md deleted file mode 100644 index e8a6e1b0..00000000 --- a/docs/framework/preact/reference/functions/useSetValue.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: useSetValue -title: useSetValue ---- - -# Function: useSetValue() - -## Call Signature - -```ts -function useSetValue(source): (fn) => void & (value) => void; -``` - -Defined in: [preact-store/src/useSetValue.ts:22](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSetValue.ts#L22) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -### Parameters - -#### source - -`Atom`\<`TValue`\> - -### Returns - -(`fn`) => `void` & (`value`) => `void` - -### Examples - -```tsx -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```tsx -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` - -## Call Signature - -```ts -function useSetValue(source): (updater) => void; -``` - -Defined in: [preact-store/src/useSetValue.ts:23](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSetValue.ts#L23) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -#### TActions - -`TActions` *extends* `StoreActionMap` - -### Parameters - -#### source - -`Store`\<`TValue`, `TActions`\> - -### Returns - -```ts -(updater): void; -``` - -#### Parameters - -##### updater - -(`prev`) => `TValue` - -#### Returns - -`void` - -### Examples - -```tsx -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```tsx -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` diff --git a/docs/framework/preact/reference/functions/useStore-1.md b/docs/framework/preact/reference/functions/useStore-1.md new file mode 100644 index 00000000..24850d6d --- /dev/null +++ b/docs/framework/preact/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, + compare?): TSelected; +``` + +Defined in: [preact-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useStore.ts#L13) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`TSelected` + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/preact/reference/functions/useStore.md b/docs/framework/preact/reference/functions/useStore.md index 24850d6d..ecfcbe95 100644 --- a/docs/framework/preact/reference/functions/useStore.md +++ b/docs/framework/preact/reference/functions/useStore.md @@ -1,61 +1,64 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# ~~Function: useStore()~~ +# Function: \_useStore() ```ts -function useStore( - source, +function _useStore( + store, selector, - compare?): TSelected; + options?): [TSelected, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [preact-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useStore.ts#L13) +Defined in: [preact-store/src/\_useStore.ts:24](https://github.com/TanStack/store/blob/main/packages/preact-store/src/_useStore.ts#L24) -Deprecated alias for [useSelector](useSelector.md). +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters -### TSource +### TState -`TSource` +`TState` -### TSelected +### TActions -`TSelected` +`TActions` *extends* `StoreActionMap` -## Parameters - -### source +### TSelected -#### get +`TSelected` = `NoInfer`\<`TState`\> -() => `TSource` +## Parameters -#### subscribe +### store -(`listener`) => `object` +`Store`\<`TState`, `TActions`\> ### selector -(`snapshot`) => `TSelected` +(`state`) => `TSelected` -### compare? +### options? -(`a`, `b`) => `boolean` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`TSelected` +\[`TSelected`, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] ## Example ```tsx -const count = useStore(counterStore, (state) => state.count) -``` +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) -## Deprecated - -Use `useSelector` instead. +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/preact/reference/functions/useStoreActions.md b/docs/framework/preact/reference/functions/useStoreActions.md deleted file mode 100644 index a2f20217..00000000 --- a/docs/framework/preact/reference/functions/useStoreActions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: useStoreActions -title: useStoreActions ---- - -# Function: useStoreActions() - -```ts -function useStoreActions(store): TActions; -``` - -Defined in: [preact-store/src/useStoreActions.ts:16](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useStoreActions.ts#L16) - -Returns the stable actions bag from a writable store created with actions. - -Use this when a component only needs to call store actions and should not -subscribe to store state. - -## Type Parameters - -### TValue - -`TValue` - -### TActions - -`TActions` *extends* `StoreActionMap` - -## Parameters - -### store - -`Store`\<`TValue`, `TActions`\> - -## Returns - -`TActions` - -## Example - -```tsx -const actions = useStoreActions(counterStore) -actions.increment() -``` diff --git a/docs/framework/preact/reference/index.md b/docs/framework/preact/reference/index.md index e243ae83..7534c172 100644 --- a/docs/framework/preact/reference/index.md +++ b/docs/framework/preact/reference/index.md @@ -11,13 +11,11 @@ title: "@tanstack/preact-store" ## Functions +- [\_useStore](functions/useStore.md) - [createStoreContext](functions/createStoreContext.md) -- [shallow](functions/shallow.md) - [useAtom](functions/useAtom.md) - [useCreateAtom](functions/useCreateAtom.md) - [useCreateStore](functions/useCreateStore.md) - [useSelector](functions/useSelector.md) -- [useSetValue](functions/useSetValue.md) -- [~~useStore~~](functions/useStore.md) -- [useStoreActions](functions/useStoreActions.md) +- [~~useStore~~](functions/useStore-1.md) - [useValue](functions/useValue.md) diff --git a/docs/framework/react/reference/functions/createStoreContext.md b/docs/framework/react/reference/functions/createStoreContext.md index 80dea06b..1515155b 100644 --- a/docs/framework/react/reference/functions/createStoreContext.md +++ b/docs/framework/react/reference/functions/createStoreContext.md @@ -16,7 +16,7 @@ Creates a typed React context for sharing a bundle of atoms and stores with a su The returned `StoreProvider` only transports the provided object through React context. Consumers destructure the contextual atoms and stores, then compose them with the existing hooks like [useSelector](useSelector.md), -[useValue](useValue.md), [useSetValue](useSetValue.md), and [useAtom](useAtom.md). +[useValue](useValue.md), useSetValue, and [useAtom](useAtom.md). The object shape is preserved exactly, so keyed atoms and stores remain fully typed when read back with `useStoreContext()`. diff --git a/docs/framework/react/reference/functions/shallow.md b/docs/framework/react/reference/functions/shallow.md deleted file mode 100644 index cef99f58..00000000 --- a/docs/framework/react/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [packages/react-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/react-store/src/shallow.ts#L1) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/react/reference/functions/useAtom.md b/docs/framework/react/reference/functions/useAtom.md index 67ab3f40..5cd2eb68 100644 --- a/docs/framework/react/reference/functions/useAtom.md +++ b/docs/framework/react/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [TValue, (fn) => void & (value) => void]; ``` -Defined in: [packages/react-store/src/useAtom.ts:17](https://github.com/TanStack/store/blob/main/packages/react-store/src/useAtom.ts#L17) +Defined in: [packages/react-store/src/useAtom.ts:16](https://github.com/TanStack/store/blob/main/packages/react-store/src/useAtom.ts#L16) Returns the current atom value together with a stable setter. diff --git a/docs/framework/react/reference/functions/useSetValue.md b/docs/framework/react/reference/functions/useSetValue.md deleted file mode 100644 index 680f1745..00000000 --- a/docs/framework/react/reference/functions/useSetValue.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -id: useSetValue -title: useSetValue ---- - -# Function: useSetValue() - -## Call Signature - -```ts -function useSetValue(source): (fn) => void & (value) => void; -``` - -Defined in: [packages/react-store/src/useSetValue.ts:23](https://github.com/TanStack/store/blob/main/packages/react-store/src/useSetValue.ts#L23) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract, supporting both direct -values and updater functions. Writable stores preserve their native -`setState` contract, supporting updater functions. - -### Type Parameters - -#### TValue - -`TValue` - -### Parameters - -#### source - -`Atom`\<`TValue`\> - -### Returns - -(`fn`) => `void` & (`value`) => `void` - -### Examples - -```tsx -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```tsx -const setState = useSetValue(appStore) -setState((prev) => ({ ...prev, count: prev.count + 1 })) -``` - -## Call Signature - -```ts -function useSetValue(source): (updater) => void; -``` - -Defined in: [packages/react-store/src/useSetValue.ts:24](https://github.com/TanStack/store/blob/main/packages/react-store/src/useSetValue.ts#L24) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract, supporting both direct -values and updater functions. Writable stores preserve their native -`setState` contract, supporting updater functions. - -### Type Parameters - -#### TValue - -`TValue` - -#### TActions - -`TActions` *extends* `StoreActionMap` - -### Parameters - -#### source - -`Store`\<`TValue`, `TActions`\> - -### Returns - -```ts -(updater): void; -``` - -#### Parameters - -##### updater - -(`prev`) => `TValue` - -#### Returns - -`void` - -### Examples - -```tsx -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```tsx -const setState = useSetValue(appStore) -setState((prev) => ({ ...prev, count: prev.count + 1 })) -``` diff --git a/docs/framework/react/reference/functions/useStore-1.md b/docs/framework/react/reference/functions/useStore-1.md new file mode 100644 index 00000000..7fab183d --- /dev/null +++ b/docs/framework/react/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, + compare?): TSelected; +``` + +Defined in: [packages/react-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStore.ts#L13) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`TSelected` + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/react/reference/functions/useStore.md b/docs/framework/react/reference/functions/useStore.md index 7fab183d..bbc43c6b 100644 --- a/docs/framework/react/reference/functions/useStore.md +++ b/docs/framework/react/reference/functions/useStore.md @@ -1,61 +1,64 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# ~~Function: useStore()~~ +# Function: \_useStore() ```ts -function useStore( - source, +function _useStore( + store, selector, - compare?): TSelected; + options?): [TSelected, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [packages/react-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStore.ts#L13) +Defined in: [packages/react-store/src/\_useStore.ts:24](https://github.com/TanStack/store/blob/main/packages/react-store/src/_useStore.ts#L24) -Deprecated alias for [useSelector](useSelector.md). +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters -### TSource +### TState -`TSource` +`TState` -### TSelected +### TActions -`TSelected` +`TActions` *extends* `StoreActionMap` -## Parameters - -### source +### TSelected -#### get +`TSelected` = `NoInfer`\<`TState`\> -() => `TSource` +## Parameters -#### subscribe +### store -(`listener`) => `object` +`Store`\<`TState`, `TActions`\> ### selector -(`snapshot`) => `TSelected` +(`state`) => `TSelected` -### compare? +### options? -(`a`, `b`) => `boolean` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`TSelected` +\[`TSelected`, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] ## Example ```tsx -const count = useStore(counterStore, (state) => state.count) -``` +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) -## Deprecated - -Use `useSelector` instead. +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/react/reference/functions/useStoreActions.md b/docs/framework/react/reference/functions/useStoreActions.md deleted file mode 100644 index 1af312ee..00000000 --- a/docs/framework/react/reference/functions/useStoreActions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: useStoreActions -title: useStoreActions ---- - -# Function: useStoreActions() - -```ts -function useStoreActions(store): TActions; -``` - -Defined in: [packages/react-store/src/useStoreActions.ts:16](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStoreActions.ts#L16) - -Returns the stable actions bag from a writable store created with actions. - -Use this when a component only needs to call store actions and should not -subscribe to state changes. - -## Type Parameters - -### TValue - -`TValue` - -### TActions - -`TActions` *extends* `StoreActionMap` - -## Parameters - -### store - -`Store`\<`TValue`, `TActions`\> - -## Returns - -`TActions` - -## Example - -```tsx -const actions = useStoreActions(counterStore) -actions.inc() -``` diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index c02db638..0505d839 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -11,13 +11,11 @@ title: "@tanstack/react-store" ## Functions +- [\_useStore](functions/useStore.md) - [createStoreContext](functions/createStoreContext.md) -- [shallow](functions/shallow.md) - [useAtom](functions/useAtom.md) - [useCreateAtom](functions/useCreateAtom.md) - [useCreateStore](functions/useCreateStore.md) - [useSelector](functions/useSelector.md) -- [useSetValue](functions/useSetValue.md) -- [~~useStore~~](functions/useStore.md) -- [useStoreActions](functions/useStoreActions.md) +- [~~useStore~~](functions/useStore-1.md) - [useValue](functions/useValue.md) diff --git a/docs/framework/solid/reference/functions/shallow.md b/docs/framework/solid/reference/functions/shallow.md deleted file mode 100644 index 1b63b6c3..00000000 --- a/docs/framework/solid/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [solid-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/solid-store/src/shallow.ts#L1) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/solid/reference/functions/useAtom.md b/docs/framework/solid/reference/functions/useAtom.md index e73a7b3b..a05c4337 100644 --- a/docs/framework/solid/reference/functions/useAtom.md +++ b/docs/framework/solid/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [Accessor, (fn) => void & (value) => void]; ``` -Defined in: [solid-store/src/useAtom.ts:24](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useAtom.ts#L24) +Defined in: [solid-store/src/useAtom.ts:23](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useAtom.ts#L23) Returns the current atom accessor together with a setter. diff --git a/docs/framework/solid/reference/functions/useSetValue.md b/docs/framework/solid/reference/functions/useSetValue.md deleted file mode 100644 index 88ab5cbf..00000000 --- a/docs/framework/solid/reference/functions/useSetValue.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: useSetValue -title: useSetValue ---- - -# Function: useSetValue() - -## Call Signature - -```ts -function useSetValue(source): (fn) => void & (value) => void; -``` - -Defined in: [solid-store/src/useSetValue.ts:21](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSetValue.ts#L21) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -### Parameters - -#### source - -`Atom`\<`TValue`\> - -### Returns - -(`fn`) => `void` & (`value`) => `void` - -### Examples - -```tsx -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```tsx -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` - -## Call Signature - -```ts -function useSetValue(source): (updater) => void; -``` - -Defined in: [solid-store/src/useSetValue.ts:22](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSetValue.ts#L22) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -#### TActions - -`TActions` *extends* `StoreActionMap` - -### Parameters - -#### source - -`Store`\<`TValue`, `TActions`\> - -### Returns - -```ts -(updater): void; -``` - -#### Parameters - -##### updater - -(`prev`) => `TValue` - -#### Returns - -`void` - -### Examples - -```tsx -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```tsx -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` diff --git a/docs/framework/solid/reference/functions/useStore-1.md b/docs/framework/solid/reference/functions/useStore-1.md new file mode 100644 index 00000000..6e73b2fc --- /dev/null +++ b/docs/framework/solid/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, +compare?): Accessor; +``` + +Defined in: [solid-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useStore.ts#L14) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`Accessor`\<`TSelected`\> + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/solid/reference/functions/useStore.md b/docs/framework/solid/reference/functions/useStore.md index 6e73b2fc..046e7a04 100644 --- a/docs/framework/solid/reference/functions/useStore.md +++ b/docs/framework/solid/reference/functions/useStore.md @@ -1,61 +1,64 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# ~~Function: useStore()~~ +# Function: \_useStore() ```ts -function useStore( - source, +function _useStore( + store, selector, -compare?): Accessor; + options?): [Accessor, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [solid-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useStore.ts#L14) +Defined in: [solid-store/src/\_useStore.ts:23](https://github.com/TanStack/store/blob/main/packages/solid-store/src/_useStore.ts#L23) -Deprecated alias for [useSelector](useSelector.md). +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters -### TSource +### TState -`TSource` +`TState` -### TSelected +### TActions -`TSelected` +`TActions` *extends* `StoreActionMap` -## Parameters - -### source +### TSelected -#### get +`TSelected` = `NoInfer`\<`TState`\> -() => `TSource` +## Parameters -#### subscribe +### store -(`listener`) => `object` +`Store`\<`TState`, `TActions`\> ### selector -(`snapshot`) => `TSelected` +(`state`) => `TSelected` -### compare? +### options? -(`a`, `b`) => `boolean` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`Accessor`\<`TSelected`\> +\[`Accessor`\<`TSelected`\>, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] ## Example ```tsx -const count = useStore(counterStore, (state) => state.count) -``` +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) -## Deprecated - -Use `useSelector` instead. +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/solid/reference/functions/useStoreActions.md b/docs/framework/solid/reference/functions/useStoreActions.md deleted file mode 100644 index 976e4a4f..00000000 --- a/docs/framework/solid/reference/functions/useStoreActions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: useStoreActions -title: useStoreActions ---- - -# Function: useStoreActions() - -```ts -function useStoreActions(store): TActions; -``` - -Defined in: [solid-store/src/useStoreActions.ts:15](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useStoreActions.ts#L15) - -Returns the stable actions bag from a writable store created with actions. - -Use this when a component only needs to call store actions and should not -subscribe to store state. - -## Type Parameters - -### TValue - -`TValue` - -### TActions - -`TActions` *extends* `StoreActionMap` - -## Parameters - -### store - -`Store`\<`TValue`, `TActions`\> - -## Returns - -`TActions` - -## Example - -```tsx -const actions = useStoreActions(counterStore) -actions.increment() -``` diff --git a/docs/framework/solid/reference/index.md b/docs/framework/solid/reference/index.md index b3b2c81c..5e9608c9 100644 --- a/docs/framework/solid/reference/index.md +++ b/docs/framework/solid/reference/index.md @@ -11,10 +11,8 @@ title: "@tanstack/solid-store" ## Functions -- [shallow](functions/shallow.md) +- [\_useStore](functions/useStore.md) - [useAtom](functions/useAtom.md) - [useSelector](functions/useSelector.md) -- [useSetValue](functions/useSetValue.md) -- [~~useStore~~](functions/useStore.md) -- [useStoreActions](functions/useStoreActions.md) +- [~~useStore~~](functions/useStore-1.md) - [useValue](functions/useValue.md) diff --git a/docs/framework/svelte/reference/functions/shallow.md b/docs/framework/svelte/reference/functions/shallow.md deleted file mode 100644 index 204a5fc6..00000000 --- a/docs/framework/svelte/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [svelte-store/src/index.svelte.ts:193](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L193) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/svelte/reference/functions/useAtom.md b/docs/framework/svelte/reference/functions/useAtom.md index d232c84f..727d42bb 100644 --- a/docs/framework/svelte/reference/functions/useAtom.md +++ b/docs/framework/svelte/reference/functions/useAtom.md @@ -11,7 +11,7 @@ function useAtom(atom, options?): [{ }, (fn) => void & (value) => void]; ``` -Defined in: [svelte-store/src/index.svelte.ts:144](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L144) +Defined in: [svelte-store/src/useAtom.ts:18](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/useAtom.ts#L18) Returns the current atom holder together with a setter. diff --git a/docs/framework/svelte/reference/functions/useSelector.md b/docs/framework/svelte/reference/functions/useSelector.md index 67c39a49..a0abbb21 100644 --- a/docs/framework/svelte/reference/functions/useSelector.md +++ b/docs/framework/svelte/reference/functions/useSelector.md @@ -12,7 +12,7 @@ function useSelector( options): object; ``` -Defined in: [svelte-store/src/index.svelte.ts:36](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L36) +Defined in: [svelte-store/src/useSelector.svelte.ts:28](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/useSelector.svelte.ts#L28) Selects a slice of state from an atom or store and exposes it through a rune-friendly holder object. diff --git a/docs/framework/svelte/reference/functions/useSetValue.md b/docs/framework/svelte/reference/functions/useSetValue.md deleted file mode 100644 index 1f94f4c9..00000000 --- a/docs/framework/svelte/reference/functions/useSetValue.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: useSetValue -title: useSetValue ---- - -# Function: useSetValue() - -## Call Signature - -```ts -function useSetValue(source): (fn) => void & (value) => void; -``` - -Defined in: [svelte-store/src/index.svelte.ts:111](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L111) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -### Parameters - -#### source - -`Atom`\<`TValue`\> - -### Returns - -(`fn`) => `void` & (`value`) => `void` - -### Examples - -```ts -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```ts -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` - -## Call Signature - -```ts -function useSetValue(source): (updater) => void; -``` - -Defined in: [svelte-store/src/index.svelte.ts:112](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L112) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -#### TActions - -`TActions` *extends* `StoreActionMap` - -### Parameters - -#### source - -`Store`\<`TValue`, `TActions`\> - -### Returns - -```ts -(updater): void; -``` - -#### Parameters - -##### updater - -(`prev`) => `TValue` - -#### Returns - -`void` - -### Examples - -```ts -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```ts -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` diff --git a/docs/framework/svelte/reference/functions/useStore-1.md b/docs/framework/svelte/reference/functions/useStore-1.md new file mode 100644 index 00000000..b3d49758 --- /dev/null +++ b/docs/framework/svelte/reference/functions/useStore-1.md @@ -0,0 +1,62 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, + compare?): object; +``` + +Defined in: [svelte-store/src/useStore.ts:15](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/useStore.ts#L15) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TState + +`TState` + +### TSelected + +`TSelected` = `NoInfer`\<`TState`\> + +## Parameters + +### source + +`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> | `Store`\<`TState`, `any`\> | `ReadonlyStore`\<`TState`\> + +### selector + +(`state`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`object` + +### ~~current~~ + +```ts +readonly current: TSelected; +``` + +## Example + +```ts +const count = useStore(counterStore, (state) => state.count) +console.log(count.current) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/svelte/reference/functions/useStore.md b/docs/framework/svelte/reference/functions/useStore.md index 0463c720..8de580f1 100644 --- a/docs/framework/svelte/reference/functions/useStore.md +++ b/docs/framework/svelte/reference/functions/useStore.md @@ -1,20 +1,26 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# ~~Function: useStore()~~ +# Function: \_useStore() ```ts -function useStore( - source, +function _useStore( + store, selector, - compare?): object; + options?): [{ + current: TSelected; +}, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [svelte-store/src/index.svelte.ts:183](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L183) +Defined in: [svelte-store/src/\_useStore.ts:21](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/_useStore.ts#L21) -Deprecated alias for [useSelector](useSelector.md). +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters @@ -22,41 +28,40 @@ Deprecated alias for [useSelector](useSelector.md). `TState` +### TActions + +`TActions` *extends* `StoreActionMap` + ### TSelected `TSelected` = `NoInfer`\<`TState`\> ## Parameters -### source +### store -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> | `Store`\<`TState`, `any`\> | `ReadonlyStore`\<`TState`\> +`Store`\<`TState`, `TActions`\> ### selector (`state`) => `TSelected` -### compare? +### options? -(`a`, `b`) => `boolean` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`object` - -### ~~current~~ - -```ts -readonly current: TSelected; -``` +\[\{ + `current`: `TSelected`; +\}, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] ## Example ```ts -const count = useStore(counterStore, (state) => state.count) -console.log(count.current) -``` +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) -## Deprecated - -Use `useSelector` instead. +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +``` diff --git a/docs/framework/svelte/reference/functions/useStoreActions.md b/docs/framework/svelte/reference/functions/useStoreActions.md deleted file mode 100644 index a8bf92db..00000000 --- a/docs/framework/svelte/reference/functions/useStoreActions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: useStoreActions -title: useStoreActions ---- - -# Function: useStoreActions() - -```ts -function useStoreActions(store): TActions; -``` - -Defined in: [svelte-store/src/index.svelte.ts:166](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L166) - -Returns the stable actions bag from a writable store created with actions. - -Use this when a component only needs to call store actions and should not -subscribe to store state. - -## Type Parameters - -### TValue - -`TValue` - -### TActions - -`TActions` *extends* `StoreActionMap` - -## Parameters - -### store - -`Store`\<`TValue`, `TActions`\> - -## Returns - -`TActions` - -## Example - -```ts -const actions = useStoreActions(counterStore) -actions.increment() -``` diff --git a/docs/framework/svelte/reference/functions/useValue.md b/docs/framework/svelte/reference/functions/useValue.md index 8e5d2915..84d3d73e 100644 --- a/docs/framework/svelte/reference/functions/useValue.md +++ b/docs/framework/svelte/reference/functions/useValue.md @@ -9,7 +9,7 @@ title: useValue function useValue(source, options?): object; ``` -Defined in: [svelte-store/src/index.svelte.ts:82](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L82) +Defined in: [svelte-store/src/useValue.ts:20](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/useValue.ts#L20) Subscribes to an atom or store and returns its whole current value through a rune-friendly holder object. diff --git a/docs/framework/svelte/reference/index.md b/docs/framework/svelte/reference/index.md index 44015402..2487c1a0 100644 --- a/docs/framework/svelte/reference/index.md +++ b/docs/framework/svelte/reference/index.md @@ -11,10 +11,8 @@ title: "@tanstack/svelte-store" ## Functions -- [shallow](functions/shallow.md) +- [\_useStore](functions/useStore.md) - [useAtom](functions/useAtom.md) - [useSelector](functions/useSelector.md) -- [useSetValue](functions/useSetValue.md) -- [~~useStore~~](functions/useStore.md) -- [useStoreActions](functions/useStoreActions.md) +- [~~useStore~~](functions/useStore-1.md) - [useValue](functions/useValue.md) diff --git a/docs/framework/svelte/reference/interfaces/UseSelectorOptions.md b/docs/framework/svelte/reference/interfaces/UseSelectorOptions.md index 0df15d70..6dfeceff 100644 --- a/docs/framework/svelte/reference/interfaces/UseSelectorOptions.md +++ b/docs/framework/svelte/reference/interfaces/UseSelectorOptions.md @@ -5,7 +5,7 @@ title: UseSelectorOptions # Interface: UseSelectorOptions\ -Defined in: [svelte-store/src/index.svelte.ts:11](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L11) +Defined in: [svelte-store/src/useSelector.svelte.ts:3](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/useSelector.svelte.ts#L3) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [svelte-store/src/index.svelte.ts:11](https://github.com/TanStack/st optional compare: (a, b) => boolean; ``` -Defined in: [svelte-store/src/index.svelte.ts:12](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L12) +Defined in: [svelte-store/src/useSelector.svelte.ts:4](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/useSelector.svelte.ts#L4) #### Parameters diff --git a/docs/framework/vue/reference/functions/useAtom.md b/docs/framework/vue/reference/functions/useAtom.md index 487eff7e..6b598d89 100644 --- a/docs/framework/vue/reference/functions/useAtom.md +++ b/docs/framework/vue/reference/functions/useAtom.md @@ -9,7 +9,7 @@ title: useAtom function useAtom(atom, options?): [Readonly>, (fn) => void & (value) => void]; ``` -Defined in: [vue-store/src/useAtom.ts:21](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useAtom.ts#L21) +Defined in: [vue-store/src/useAtom.ts:20](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useAtom.ts#L20) Returns the current atom ref together with a setter. diff --git a/docs/framework/vue/reference/functions/useSetValue.md b/docs/framework/vue/reference/functions/useSetValue.md deleted file mode 100644 index 0be3c06b..00000000 --- a/docs/framework/vue/reference/functions/useSetValue.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: useSetValue -title: useSetValue ---- - -# Function: useSetValue() - -## Call Signature - -```ts -function useSetValue(source): (fn) => void & (value) => void; -``` - -Defined in: [vue-store/src/useSetValue.ts:21](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSetValue.ts#L21) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -### Parameters - -#### source - -`Atom`\<`TValue`\> - -### Returns - -(`fn`) => `void` & (`value`) => `void` - -### Examples - -```ts -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```ts -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` - -## Call Signature - -```ts -function useSetValue(source): (updater) => void; -``` - -Defined in: [vue-store/src/useSetValue.ts:22](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useSetValue.ts#L22) - -Returns a stable setter for a writable atom or store. - -Writable atoms preserve their native `set` contract. Writable stores -preserve their native `setState` contract. - -### Type Parameters - -#### TValue - -`TValue` - -#### TActions - -`TActions` *extends* `StoreActionMap` - -### Parameters - -#### source - -`Store`\<`TValue`, `TActions`\> - -### Returns - -```ts -(updater): void; -``` - -#### Parameters - -##### updater - -(`prev`) => `TValue` - -#### Returns - -`void` - -### Examples - -```ts -const setCount = useSetValue(countAtom) -setCount((prev) => prev + 1) -``` - -```ts -const setState = useSetValue(counterStore) -setState((state) => ({ ...state, count: state.count + 1 })) -``` diff --git a/docs/framework/vue/reference/functions/useStore-1.md b/docs/framework/vue/reference/functions/useStore-1.md new file mode 100644 index 00000000..ab199948 --- /dev/null +++ b/docs/framework/vue/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, +compare?): Readonly>; +``` + +Defined in: [vue-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useStore.ts#L14) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`Readonly`\<`Ref`\<`TSelected`\>\> + +## Example + +```ts +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/vue/reference/functions/useStore.md b/docs/framework/vue/reference/functions/useStore.md index ab199948..0a14ea38 100644 --- a/docs/framework/vue/reference/functions/useStore.md +++ b/docs/framework/vue/reference/functions/useStore.md @@ -1,61 +1,65 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# ~~Function: useStore()~~ +# Function: \_useStore() ```ts -function useStore( - source, +function _useStore( + store, selector, -compare?): Readonly>; + options?): [Readonly>, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [vue-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useStore.ts#L14) +Defined in: [vue-store/src/\_useStore.ts:24](https://github.com/TanStack/store/blob/main/packages/vue-store/src/_useStore.ts#L24) -Deprecated alias for [useSelector](useSelector.md). +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters -### TSource +### TState -`TSource` +`TState` -### TSelected +### TActions -`TSelected` +`TActions` *extends* `StoreActionMap` -## Parameters - -### source +### TSelected -#### get +`TSelected` = `NoInfer`\<`TState`\> -() => `TSource` +## Parameters -#### subscribe +### store -(`listener`) => `object` +`Store`\<`TState`, `TActions`\> ### selector -(`snapshot`) => `TSelected` +(`state`) => `TSelected` -### compare? +### options? -(`a`, `b`) => `boolean` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`Readonly`\<`Ref`\<`TSelected`\>\> +\[`Readonly`\<`Ref`\<`TSelected`, `TSelected`\>\>, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] ## Example ```ts -const count = useStore(counterStore, (state) => state.count) -``` +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) +console.log(cats.value) -## Deprecated - -Use `useSelector` instead. +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/vue/reference/functions/useStoreActions.md b/docs/framework/vue/reference/functions/useStoreActions.md deleted file mode 100644 index 122a9f8e..00000000 --- a/docs/framework/vue/reference/functions/useStoreActions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: useStoreActions -title: useStoreActions ---- - -# Function: useStoreActions() - -```ts -function useStoreActions(store): TActions; -``` - -Defined in: [vue-store/src/useStoreActions.ts:15](https://github.com/TanStack/store/blob/main/packages/vue-store/src/useStoreActions.ts#L15) - -Returns the stable actions bag from a writable store created with actions. - -Use this when a component only needs to call store actions and should not -subscribe to store state. - -## Type Parameters - -### TValue - -`TValue` - -### TActions - -`TActions` *extends* `StoreActionMap` - -## Parameters - -### store - -`Store`\<`TValue`, `TActions`\> - -## Returns - -`TActions` - -## Example - -```ts -const actions = useStoreActions(counterStore) -actions.increment() -``` diff --git a/docs/framework/vue/reference/index.md b/docs/framework/vue/reference/index.md index 95a1ad3b..1c654737 100644 --- a/docs/framework/vue/reference/index.md +++ b/docs/framework/vue/reference/index.md @@ -11,10 +11,8 @@ title: "@tanstack/vue-store" ## Functions -- [shallow](functions/shallow.md) +- [\_useStore](functions/useStore.md) - [useAtom](functions/useAtom.md) - [useSelector](functions/useSelector.md) -- [useSetValue](functions/useSetValue.md) -- [~~useStore~~](functions/useStore.md) -- [useStoreActions](functions/useStoreActions.md) +- [~~useStore~~](functions/useStore-1.md) - [useValue](functions/useValue.md) diff --git a/docs/reference/classes/ReadonlyStore.md b/docs/reference/classes/ReadonlyStore.md index 99b0279c..410262b2 100644 --- a/docs/reference/classes/ReadonlyStore.md +++ b/docs/reference/classes/ReadonlyStore.md @@ -5,7 +5,7 @@ title: ReadonlyStore # Class: ReadonlyStore\ -Defined in: [store.ts:61](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L61) +Defined in: [store.ts:57](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L57) ## Type Parameters @@ -25,7 +25,7 @@ Defined in: [store.ts:61](https://github.com/TanStack/store/blob/main/packages/s new ReadonlyStore(getValue): ReadonlyStore; ``` -Defined in: [store.ts:66](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L66) +Defined in: [store.ts:62](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L62) #### Parameters @@ -43,7 +43,7 @@ Defined in: [store.ts:66](https://github.com/TanStack/store/blob/main/packages/s new ReadonlyStore(initialValue): ReadonlyStore; ``` -Defined in: [store.ts:67](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L67) +Defined in: [store.ts:63](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L63) #### Parameters @@ -65,7 +65,7 @@ Defined in: [store.ts:67](https://github.com/TanStack/store/blob/main/packages/s get state(): T; ``` -Defined in: [store.ts:75](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L75) +Defined in: [store.ts:71](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L71) ##### Returns @@ -85,7 +85,7 @@ Omit.state get(): T; ``` -Defined in: [store.ts:78](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L78) +Defined in: [store.ts:74](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L74) #### Returns @@ -105,7 +105,7 @@ Omit.get subscribe(observerOrFn): Subscription; ``` -Defined in: [store.ts:81](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L81) +Defined in: [store.ts:77](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L77) #### Parameters diff --git a/docs/reference/classes/Store.md b/docs/reference/classes/Store.md index 8106e9ac..2c52f96d 100644 --- a/docs/reference/classes/Store.md +++ b/docs/reference/classes/Store.md @@ -5,7 +5,7 @@ title: Store # Class: Store\ -Defined in: [store.ts:19](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L19) +Defined in: [store.ts:15](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L15) ## Type Parameters @@ -25,7 +25,7 @@ Defined in: [store.ts:19](https://github.com/TanStack/store/blob/main/packages/s new Store(getValue): Store; ``` -Defined in: [store.ts:22](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L22) +Defined in: [store.ts:18](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L18) #### Parameters @@ -43,7 +43,7 @@ Defined in: [store.ts:22](https://github.com/TanStack/store/blob/main/packages/s new Store(initialValue): Store; ``` -Defined in: [store.ts:23](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L23) +Defined in: [store.ts:19](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L19) #### Parameters @@ -61,7 +61,7 @@ Defined in: [store.ts:23](https://github.com/TanStack/store/blob/main/packages/s new Store(initialValue, actionsFactory): Store; ``` -Defined in: [store.ts:24](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L24) +Defined in: [store.ts:20](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L20) #### Parameters @@ -85,7 +85,7 @@ Defined in: [store.ts:24](https://github.com/TanStack/store/blob/main/packages/s readonly actions: TActions; ``` -Defined in: [store.ts:21](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L21) +Defined in: [store.ts:17](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L17) ## Accessors @@ -97,7 +97,7 @@ Defined in: [store.ts:21](https://github.com/TanStack/store/blob/main/packages/s get state(): T; ``` -Defined in: [store.ts:48](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L48) +Defined in: [store.ts:44](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L44) ##### Returns @@ -111,7 +111,7 @@ Defined in: [store.ts:48](https://github.com/TanStack/store/blob/main/packages/s get(): T; ``` -Defined in: [store.ts:51](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L51) +Defined in: [store.ts:47](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L47) #### Returns @@ -125,7 +125,7 @@ Defined in: [store.ts:51](https://github.com/TanStack/store/blob/main/packages/s setState(updater): void; ``` -Defined in: [store.ts:45](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L45) +Defined in: [store.ts:41](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L41) #### Parameters @@ -145,7 +145,7 @@ Defined in: [store.ts:45](https://github.com/TanStack/store/blob/main/packages/s subscribe(observerOrFn): Subscription; ``` -Defined in: [store.ts:54](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L54) +Defined in: [store.ts:50](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L50) #### Parameters diff --git a/docs/reference/functions/createStore.md b/docs/reference/functions/createStore.md index 0420f5e3..080a10c3 100644 --- a/docs/reference/functions/createStore.md +++ b/docs/reference/functions/createStore.md @@ -11,7 +11,7 @@ title: createStore function createStore(getValue): ReadonlyStore; ``` -Defined in: [store.ts:88](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L88) +Defined in: [store.ts:84](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L84) ### Type Parameters @@ -35,7 +35,7 @@ Defined in: [store.ts:88](https://github.com/TanStack/store/blob/main/packages/s function createStore(initialValue): Store; ``` -Defined in: [store.ts:91](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L91) +Defined in: [store.ts:87](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L87) ### Type Parameters @@ -59,7 +59,7 @@ Defined in: [store.ts:91](https://github.com/TanStack/store/blob/main/packages/s function createStore(initialValue, actions): Store; ``` -Defined in: [store.ts:92](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L92) +Defined in: [store.ts:88](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L88) ### Type Parameters diff --git a/docs/framework/vue/reference/functions/shallow.md b/docs/reference/functions/shallow.md similarity index 63% rename from docs/framework/vue/reference/functions/shallow.md rename to docs/reference/functions/shallow.md index 10e38b27..fb3b313a 100644 --- a/docs/framework/vue/reference/functions/shallow.md +++ b/docs/reference/functions/shallow.md @@ -9,7 +9,7 @@ title: shallow function shallow(objA, objB): boolean; ``` -Defined in: [vue-store/src/shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/vue-store/src/shallow.ts#L1) +Defined in: [shallow.ts:1](https://github.com/TanStack/store/blob/main/packages/store/src/shallow.ts#L1) ## Type Parameters diff --git a/docs/reference/index.md b/docs/reference/index.md index 9dae1148..afc8e5dd 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -20,7 +20,6 @@ title: "@tanstack/store" - [InteropSubscribable](interfaces/InteropSubscribable.md) - [Readable](interfaces/Readable.md) - [ReadonlyAtom](interfaces/ReadonlyAtom.md) -- [StoreActionsApi](interfaces/StoreActionsApi.md) - [Subscribable](interfaces/Subscribable.md) - [Subscription](interfaces/Subscription.md) @@ -40,4 +39,5 @@ title: "@tanstack/store" - [createAtom](functions/createAtom.md) - [createStore](functions/createStore.md) - [flush](functions/flush.md) +- [shallow](functions/shallow.md) - [toObserver](functions/toObserver.md) diff --git a/docs/reference/interfaces/StoreActionsApi.md b/docs/reference/interfaces/StoreActionsApi.md deleted file mode 100644 index 450fc1cc..00000000 --- a/docs/reference/interfaces/StoreActionsApi.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: StoreActionsApi -title: StoreActionsApi ---- - -# Interface: StoreActionsApi\ - -Defined in: [store.ts:4](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L4) - -## Type Parameters - -### T - -`T` - -## Properties - -### get() - -```ts -get: () => T; -``` - -Defined in: [store.ts:6](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L6) - -#### Returns - -`T` - -*** - -### set() - -```ts -set: (updater) => void; -``` - -Defined in: [store.ts:5](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L5) - -#### Parameters - -##### updater - -(`prev`) => `T` - -#### Returns - -`void` diff --git a/docs/reference/type-aliases/StoreAction.md b/docs/reference/type-aliases/StoreAction.md index edddc7b1..1e839025 100644 --- a/docs/reference/type-aliases/StoreAction.md +++ b/docs/reference/type-aliases/StoreAction.md @@ -9,7 +9,7 @@ title: StoreAction type StoreAction = (...args) => any; ``` -Defined in: [store.ts:9](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L9) +Defined in: [store.ts:4](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L4) ## Parameters diff --git a/docs/reference/type-aliases/StoreActionMap.md b/docs/reference/type-aliases/StoreActionMap.md index 2b06abbd..5191c5f5 100644 --- a/docs/reference/type-aliases/StoreActionMap.md +++ b/docs/reference/type-aliases/StoreActionMap.md @@ -9,4 +9,4 @@ title: StoreActionMap type StoreActionMap = Record; ``` -Defined in: [store.ts:11](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L11) +Defined in: [store.ts:6](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L6) diff --git a/docs/reference/type-aliases/StoreActionsFactory.md b/docs/reference/type-aliases/StoreActionsFactory.md index 037434fd..b7caaee8 100644 --- a/docs/reference/type-aliases/StoreActionsFactory.md +++ b/docs/reference/type-aliases/StoreActionsFactory.md @@ -6,10 +6,10 @@ title: StoreActionsFactory # Type Alias: StoreActionsFactory()\ ```ts -type StoreActionsFactory = (api) => TActions; +type StoreActionsFactory = (store) => TActions; ``` -Defined in: [store.ts:13](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L13) +Defined in: [store.ts:8](https://github.com/TanStack/store/blob/main/packages/store/src/store.ts#L8) ## Type Parameters @@ -23,9 +23,15 @@ Defined in: [store.ts:13](https://github.com/TanStack/store/blob/main/packages/s ## Parameters -### api +### store -[`StoreActionsApi`](../interfaces/StoreActionsApi.md)\<`T`\> +#### get + +[`Store`](../classes/Store.md)\<`T`\>\[`"get"`\] + +#### setState + +[`Store`](../classes/Store.md)\<`T`\>\[`"setState"`\] ## Returns