valtio-define 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![coverage][coverage-src]][coverage-href]
8
8
  [![License][license-src]][license-href].
9
9
 
10
- ⚡️ **valtio-define** is a lightweight factory for creating fully functional, robust [Valtio](https://valtio.dev) stores. It simplifies state management in React by providing a structured API for state, actions, and computed getters.
10
+ ⚡️ **valtio-define** is a lightweight factory for creating fully functional, robust [Valtio](https://github.com/pmndrs/valtio) stores. It simplifies state management in React by providing a structured API for state, actions, and computed getters.
11
11
 
12
12
  ## 📦 Installation
13
13
 
@@ -15,8 +15,18 @@
15
15
  pnpm add valtio-define
16
16
  ```
17
17
 
18
+ <details>
19
+ <summary>Are you an AI?</summary>
20
+
21
+ You can run <code>npx skills add hairyf/valtio-define --skill valtio-define -y</code> to install the skills!
22
+
23
+ Remember to ask your owner which <a href="https://github.com/vercel-labs/skills?tab=readme-ov-file#supported-agents">Agents</a> need to be supported (using the <code>--agent</code> parameter of skills) to avoid installing too many default <code>agents</code> directories.
24
+
25
+ </details>
26
+
18
27
  ## 🚀 Quick Start
19
28
 
29
+
20
30
  ### Basic Store
21
31
 
22
32
  `defineStore` allows you to encapsulate state and logic in one place. Use `useStore` to consume the reactive state in your components.
@@ -109,7 +119,7 @@ Save and restore your store state using the `persist` plugin.
109
119
 
110
120
  ```tsx
111
121
  import valtio from 'valtio-define'
112
- import { persist } from 'valtio-define/plugins'
122
+ import { persist } from 'valtio-define/plugins/persist'
113
123
 
114
124
  valtio.use(persist())
115
125
  ```
@@ -161,6 +171,28 @@ function Profile() {
161
171
  }
162
172
  ```
163
173
 
174
+ ### Controlled inputs may lose caret position
175
+
176
+ Ref: https://github.com/pmndrs/valtio/issues/270
177
+
178
+ This happens because Valtio batches state updates causing React to re-render after the input event. React resets the DOM value and loses the caret position.
179
+
180
+ Use `{ sync: true }` to update synchronously and preserve the caret:
181
+
182
+ ```tsx
183
+ function Input() {
184
+ const { text } = useStore(store, { sync: true })
185
+ // const [text, setText] = storeToState(store, 'text', { sync: true })
186
+
187
+ return (
188
+ <input
189
+ onChange={e => store.text = e.target.value}
190
+ value={text}
191
+ />
192
+ )
193
+ }
194
+ ```
195
+
164
196
  -----
165
197
 
166
198
  ## 🛰 Store API
@@ -170,10 +202,30 @@ Every store instance created with `defineStore` includes built-in utility method
170
202
  * **`$patch(obj | fn)`**: Bulk update the state.
171
203
  * **`$subscribe(callback)`**: Watch the entire store for changes.
172
204
  * **`$subscribeKey(key, callback)`**: Watch a specific property.
173
- * **`$signal(selector)`**: Use a selector function inside JSX for fine-grained reactivity.
205
+ * **`$signal(selector)`**: Use a selector function to create a signal.
174
206
 
175
207
  -----
176
208
 
209
+ ### 📡 Signal
210
+
211
+ > if you want to use the signal plugin, you need to import it and use it in your store.
212
+ ```tsx
213
+ import { valtio } from 'valtio-define'
214
+ import { signal } from 'valtio-define/plugins/signal'
215
+
216
+ valtio.use(signal())
217
+ ```
218
+
219
+ And add jsxImportSource at the beginning of your `.tsx` file
220
+
221
+ ```tsx
222
+ /** @jsxImportSource valtio-signal */
223
+
224
+ function App() {
225
+ return <div>{store.$signal(state => state.count)}</div>
226
+ }
227
+ ```
228
+
177
229
  ## 🔌 Plugins
178
230
 
179
231
  ### Global vs. Per-Store
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as GettersReturnType, c as PluginContext, d as StoreDefine, f as StoreDefineOptions, h as SubscribeKey, i as Getters, l as Signal, m as Subscribe, n as ActionsOmitThisParameter, o as Patch, p as StoreOptions, r as ActionsTree, s as Plugin, t as Actions, u as Store } from "./types-CqDu3QIJ.mjs";
1
+ import { _ as SubscribeKey, a as GettersReturnType, c as Path, d as Signal, f as Store, g as Subscribe, h as StoreOptions, i as Getters, l as Plugin, m as StoreDefineOptions, n as ActionsOmitThisParameter, o as Op, p as StoreDefine, r as ActionsTree, s as Patch, t as Actions, u as PluginContext } from "./types-Cu8iJxyS.mjs";
2
2
  import { Snapshot } from "valtio";
3
3
  import { Dispatch, SetStateAction } from "react";
4
4
 
@@ -35,21 +35,27 @@ declare function use(plugin: Plugin): void;
35
35
  *
36
36
  * ```
37
37
  */
38
- declare function defineStore<S extends object = {}, A extends Actions<S> = {}, G extends Getters<S> = {}>(define: StoreDefine<S, A, G>): Store<S, A, G>;
38
+ declare function defineStore<S extends object = {}, A extends Actions<S> = {}, G extends Getters<S> = {}>(options: StoreDefine<S, A, G>): Store<S, A, G>;
39
39
  //#endregion
40
40
  //#region src/hooks.d.ts
41
- interface UseStoreOptions {
41
+ declare function useStore<S extends object, A extends Actions<S>, G extends Getters<S>>(store: Store<S, A, G>, options?: {
42
42
  sync?: boolean;
43
- }
44
- declare function useStore<S extends object, A extends Actions<S>, G extends Getters<S>>(store: Store<S, A, G>, options?: UseStoreOptions): Snapshot<S & GettersReturnType<G> & A>;
43
+ }): Snapshot<S & GettersReturnType<G> & A>;
44
+ declare function useGetters<S extends object, G extends Getters<S>>(store: Store<S, any, G>, options?: {
45
+ sync?: boolean;
46
+ }): GettersReturnType<G>;
45
47
  //#endregion
46
48
  //#region src/utils.d.ts
47
- declare function storeToState<S extends object, K extends keyof S>(store: Store<S>, key: K): [S[K], Dispatch<SetStateAction<S[K]>>];
48
- declare function storeToStates<S extends object>(store: Store<S>): { [K in keyof S]: [S[K], Dispatch<SetStateAction<S[K]>>] };
49
+ declare function storeToState<S extends object, K extends keyof S>(store: Store<S>, key: K, options?: {
50
+ sync?: boolean;
51
+ }): [S[K], Dispatch<SetStateAction<S[K]>>];
52
+ declare function storeToStates<S extends object>(store: Store<S>, options?: {
53
+ sync?: boolean;
54
+ }): { [K in keyof S]: [S[K], Dispatch<SetStateAction<S[K]>>] };
49
55
  //#endregion
50
56
  //#region src/index.d.ts
51
57
  declare const _default: {
52
58
  use: typeof use;
53
59
  };
54
60
  //#endregion
55
- export { Actions, ActionsOmitThisParameter, ActionsTree, Getters, GettersReturnType, Patch, Plugin, PluginContext, Signal, Store, StoreDefine, StoreDefineOptions, StoreOptions, Subscribe, SubscribeKey, UseStoreOptions, _default as default, defineStore, plugins, storeToState, storeToStates, use, useStore };
61
+ export { Actions, ActionsOmitThisParameter, ActionsTree, Getters, GettersReturnType, Op, Patch, Path, Plugin, PluginContext, Signal, Store, StoreDefine, StoreDefineOptions, StoreOptions, Subscribe, SubscribeKey, _default as default, defineStore, plugins, storeToState, storeToStates, use, useGetters, useStore };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{proxy as e,ref as t,subscribe as n,useSnapshot as r}from"valtio";import{createElement as i}from"react";import{subscribeKey as a}from"valtio/utils";const o=e([]);function s(e){o.push(e)}function c(s){let c=typeof s.state==`function`?s.state():s.state,u=s.getters||{},d=s.actions||{},f=e(c),p={},m={},h=new WeakSet;for(let e in d)p[e]=t(d[e].bind(f)),f[e]=p[e];for(let e in u)l(f,e,()=>u[e].call(f)),l(m,e,()=>f[e]);function g(e){return n(f,t=>e(f,t))}function _(e,t){return a(f,e,e=>t(e),!0)}function v(e){typeof e==`function`?e(f):Object.assign(f,e)}function y(e){return i(()=>e(r(f)))}function b(e){o.push(e),C(e)}let x={$subscribe:g,$subscribeKey:_,$patch:v,$state:f,$actions:p,$getters:m,$signal:y,use:b},S=new Proxy(x,{get(e,t){return t in p?p[t]:t in e?e[t]:f[t]},has(e,t){return t in e||t in p||t in f},set(e,t,n){return t in f?f[t]=n:e[t]=n,!0}});function C(e){h.has(e)||(h.add(e),e({store:S,options:s}))}for(let e of o)C(e);return n(o,()=>{for(let e of o)C(e)}),S}function l(e,t,n){Object.defineProperty(e,t,{get:n,enumerable:!0})}function u(e,t){return r(e.$state,t)}function d(e,t){let n=u(e);function r(n){typeof n==`function`?e.$patch(e=>{e[t]=n(e[t])}):e.$patch(e=>{e[t]=n})}return[n[t],r]}function f(e){return Object.fromEntries(Object.keys(e.$state).map(t=>[t,d(e,t)]))}var p={use:s};export{p as default,c as defineStore,o as plugins,d as storeToState,f as storeToStates,s as use,u as useStore};
1
+ import{proxy as e,useSnapshot as t}from"valtio";import{computed as n}from"valtio-reactive";import{$ as r}from"valtio-signal";import{subscribeKey as i}from"valtio/utils";import{proxy as a,ref as o,subscribe as s}from"valtio/vanilla";const c=e([]);function l(e){c.push(e)}function u(e){let t=typeof e.state==`function`?e.state():e.state,l=e.getters||{},u=e.actions||{},p=a(t),m,h={},g=new WeakSet,_=n(f(l,p));for(let e of Object.keys(_))d(p,e,()=>_[e],{enumerable:!1});for(let e in u)h[e]=o(u[e].bind(p)),d(p,e,()=>h[e],{enumerable:!1});function v(e){return s(p,t=>e(p,t))}function y(e,t){return i(p,e,e=>t(e),!0)}function b(e){typeof e==`function`?e(p):Object.assign(p,e)}function x(e){return e(r(p))}function S(){m?.()}function C(e){E(e)}let w={$subscribe:v,$subscribeKey:y,$patch:b,$state:p,$actions:h,$getters:_,$dispose:S,$signal:x,use:C},T=new Proxy(w,{get(e,t){return t in h?h[t]:t in e?e[t]:p[t]},has(e,t){return t in e||t in h||t in p},set(e,t,n){return t in p?p[t]=n:e[t]=n,!0}});function E(t){g.has(t)||(g.add(t),t({store:T,options:e}))}for(let e of c)E(e);return m=s(c,()=>{for(let e of c)E(e)}),T}function d(e,t,n,r){Object.defineProperty(e,t,{get:n,enumerable:!0,...r})}function f(e,t){let n={};for(let r in e)n[r]=e[r].bind(t);return n}function p(e,n){return t(e.$state,n)}function m(e,n){return t(e.$getters,n)}function h(e,t,n){let r=p(e,n);function i(n){typeof n==`function`?e.$patch(e=>{e[t]=n(e[t])}):e.$patch(e=>{e[t]=n})}return[r[t],i]}function g(e,t){return Object.fromEntries(Object.keys(e.$state).map(n=>[n,h(e,n,t)]))}var _={use:l};export{_ as default,u as defineStore,c as plugins,h as storeToState,g as storeToStates,l as use,m as useGetters,p as useStore};
@@ -1,2 +1,3 @@
1
- import { PersistentMountOptions, StorePersistentOptions, persist } from "./persist/index.mjs";
2
- export { PersistentMountOptions, StorePersistentOptions, persist };
1
+ import { PersistentMountOptions, persist } from "./persist/index.mjs";
2
+ import { signal } from "./signal/index.mjs";
3
+ export { PersistentMountOptions, persist, signal };
@@ -1 +1 @@
1
- import{persist as e}from"./persist/index.mjs";export{e as persist};
1
+ import{persist as e}from"./persist/index.mjs";import{signal as t}from"./signal/index.mjs";export{e as persist,t as signal};
@@ -1,4 +1,4 @@
1
- import { s as Plugin } from "../../types-CqDu3QIJ.mjs";
1
+ import { l as Plugin } from "../../types-Cu8iJxyS.mjs";
2
2
  import { PersistentOptions, PersistentStore } from "./types.mjs";
3
3
 
4
4
  //#region src/plugins/persist/index.d.ts
@@ -8,9 +8,6 @@ interface PersistentMountOptions {
8
8
  */
9
9
  hydrate?: boolean;
10
10
  }
11
- interface StorePersistentOptions {
12
- mount: () => void;
13
- }
14
11
  declare function persist({
15
12
  hydrate
16
13
  }?: PersistentMountOptions): Plugin;
@@ -18,7 +15,7 @@ declare module 'valtio-define' {
18
15
  interface StoreDefineOptions<S extends object> {
19
16
  persist?: PersistentOptions<S> | boolean;
20
17
  }
21
- interface StoreOptions extends PersistentStore {}
18
+ interface StoreOptions<S, A, G> extends PersistentStore {}
22
19
  }
23
20
  //#endregion
24
- export { PersistentMountOptions, StorePersistentOptions, persist };
21
+ export { PersistentMountOptions, persist };
@@ -1 +1 @@
1
- import{subscribe as e}from"valtio";import{get as t,set as n}from"@hairy/utils";import{destr as r}from"destr";import{generateStructureId as i}from"structure-id";function a({hydrate:a=!0}={}){return o=>{let{persist:s,getters:c}=o.options,{$state:l}=o.store;if(!s)return;let u=s===!0?{}:s,d=u.key||i(l),f=u.storage??(typeof localStorage<`u`?localStorage:void 0);if(!f?.getItem||!f?.setItem)return;let p=!1;function m(e){let t=r(e);t&&typeof t==`object`&&(Object.keys(c||{}).forEach(e=>Reflect.deleteProperty(t,e)),Object.assign(l,t)),p=!0}function h(){let e=f.getItem(d);e instanceof Promise?e.then(m):m(e)}function g(){e(l,()=>{if(!p)return;let e=(u.paths||Object.keys(l)).reduce((e,r)=>n(e,r,t(l,r)),{});f.setItem(d,JSON.stringify(e))})}o.store.$persist={mount:h},a&&h(),g()}}export{a as persist};
1
+ import{subscribe as e}from"valtio";import{get as t,set as n}from"@hairy/utils";import{destr as r}from"destr";import{generateStructureId as i}from"structure-id";function a({hydrate:a=!0}={}){return o=>{let{persist:s,getters:c}=o.options,{$state:l}=o.store;if(o.store.$persist?.unmount?.(),!s)return;let u=s===!0?{}:s,d=u.key||i(l),f=u.storage??(typeof localStorage<`u`?localStorage:void 0);if(!f?.getItem||!f?.setItem)return;let p=o.store.$persist?.meta??{mounted:!1,hydrated:!1};function m(e){let t=r(e);t&&typeof t==`object`&&(Object.keys(c||{}).forEach(e=>Reflect.deleteProperty(t,e)),Object.assign(l,t)),p.hydrated=!0}function h(){p.mounted=!0;let e=f.getItem(d);e instanceof Promise?e.then(m):m(e)}function g(){p.unsubscribe?.(),p.unsubscribe=void 0}function _(){g(),p.unsubscribe=e(l,()=>{if(!p.hydrated)return;let e=(u.paths||Object.keys(l)).reduce((e,r)=>n(e,r,t(l,r)),{});f.setItem(d,JSON.stringify(e))})}o.store.$persist={mount:h,unmount:g,meta:p},a&&!p.mounted&&h(),_()}}export{a as persist};
@@ -12,10 +12,17 @@ interface PersistentOptions<S extends object> {
12
12
  storage?: Storage;
13
13
  paths?: DeepKeys<S>[];
14
14
  }
15
+ interface PersistentMeta {
16
+ mounted: boolean;
17
+ hydrated: boolean;
18
+ unsubscribe?: () => void;
19
+ }
15
20
  interface PersistentStore {
16
21
  $persist: {
17
22
  mount: () => void;
23
+ unmount: () => void;
24
+ meta: PersistentMeta;
18
25
  };
19
26
  }
20
27
  //#endregion
21
- export { DeepKeys, PersistentOptions, PersistentStore, Storage };
28
+ export { DeepKeys, PersistentMeta, PersistentOptions, PersistentStore, Storage };
@@ -0,0 +1,10 @@
1
+ import { i as Getters, l as Plugin } from "../../types-Cu8iJxyS.mjs";
2
+ import { SignalStore } from "./types.mjs";
3
+
4
+ //#region src/plugins/signal/index.d.ts
5
+ declare function signal(): Plugin;
6
+ declare module 'valtio-define' {
7
+ interface StoreOptions<S, A, G extends Getters<S>> extends SignalStore<S, G> {}
8
+ }
9
+ //#endregion
10
+ export { signal };
@@ -0,0 +1 @@
1
+ import{$ as e}from"valtio-signal";function t(){return t=>{function n(n){let r=e(t.store.$state);return n?n(r):r}t.store.$signal=n}}export{t as signal};
@@ -0,0 +1,2 @@
1
+ import { Fragment, jsx, jsxDEV, jsxs } from "./jsx-runtime.mjs";
2
+ export { Fragment, jsx, jsxDEV, jsxs };
@@ -0,0 +1 @@
1
+ import{Fragment as e,jsx as t,jsxDEV as n,jsxs as r}from"./jsx-runtime.mjs";export{e as Fragment,t as jsx,n as jsxDEV,r as jsxs};
@@ -0,0 +1,11 @@
1
+ import { Fragment, jsx, jsxDEV, jsxs } from "valtio-signal/jsx-runtime";
2
+
3
+ //#region src/plugins/signal/jsx-runtime.d.ts
4
+ declare global {
5
+ namespace JSX {
6
+ interface IntrinsicElements extends React.JSX.IntrinsicElements {}
7
+ interface Element extends React.ReactElement<any, any> {}
8
+ }
9
+ }
10
+ //#endregion
11
+ export { Fragment, jsx, jsxDEV, jsxs };
@@ -0,0 +1 @@
1
+ import{Fragment as e,jsx as t,jsxDEV as n,jsxs as r}from"valtio-signal/jsx-runtime";export{e as Fragment,t as jsx,n as jsxDEV,r as jsxs};
@@ -0,0 +1,12 @@
1
+ import { a as GettersReturnType, i as Getters } from "../../types-Cu8iJxyS.mjs";
2
+
3
+ //#region src/plugins/signal/types.d.ts
4
+ interface Signal<S> {
5
+ (): S;
6
+ <T>(fn: (state: S) => T): T;
7
+ }
8
+ interface SignalStore<S, G extends Getters<S>> {
9
+ $signal: Signal<S & GettersReturnType<G>>;
10
+ }
11
+ //#endregion
12
+ export { Signal, SignalStore };
@@ -0,0 +1 @@
1
+ export{};
@@ -18,7 +18,7 @@ interface Signal<S, G extends Getters<S>> {
18
18
  type Path = (string | symbol)[];
19
19
  type Op = [op: 'set', path: Path, value: unknown, prevValue: unknown] | [op: 'delete', path: Path, prevValue: unknown];
20
20
  interface Subscribe<S, G extends Getters<S>> {
21
- (listener: (state: S & GettersReturnType<G>, opts: Op) => void): () => void;
21
+ (listener: (state: S & GettersReturnType<G>, opts: Op[]) => void): () => void;
22
22
  }
23
23
  interface SubscribeKey<S, G extends Getters<S>> {
24
24
  <T extends keyof S | keyof G>(key: T, listener: (state: (S & GettersReturnType<G>)[T]) => void): () => void;
@@ -26,17 +26,17 @@ interface SubscribeKey<S, G extends Getters<S>> {
26
26
  interface Patch<S, G extends Getters<S>> {
27
27
  (patch: Partial<S> | ((state: S & GettersReturnType<G>) => void)): void;
28
28
  }
29
- interface StoreOptions {}
29
+ interface StoreOptions<S, A, G> {}
30
30
  type Store<S, A extends Actions<S> = {}, G extends Getters<S> = {}> = {
31
31
  $subscribe: Subscribe<S, G>;
32
32
  $subscribeKey: SubscribeKey<S, G>;
33
33
  $patch: Patch<S, G>;
34
- $state: S & GettersReturnType<G> & ActionsOmitThisParameter<A>;
34
+ $state: S;
35
35
  $actions: ActionsOmitThisParameter<A>;
36
36
  $getters: GettersReturnType<G>;
37
+ $dispose: () => void;
37
38
  use: (plugin: Plugin) => void;
38
- $signal: Signal<S, G>;
39
- } & S & GettersReturnType<G> & ActionsOmitThisParameter<A> & StoreOptions;
39
+ } & S & GettersReturnType<G> & ActionsOmitThisParameter<A> & StoreOptions<S, A, G>;
40
40
  interface PluginContext<S extends object = Record<string, unknown>> {
41
41
  store: Store<S, Actions<S>, Getters<S>>;
42
42
  options: StoreDefine<S, ActionsTree, Getters<S>>;
@@ -45,4 +45,4 @@ interface Plugin {
45
45
  <S extends object = Record<string, unknown>>(context: PluginContext<S>): void;
46
46
  }
47
47
  //#endregion
48
- export { GettersReturnType as a, PluginContext as c, StoreDefine as d, StoreDefineOptions as f, SubscribeKey as h, Getters as i, Signal as l, Subscribe as m, ActionsOmitThisParameter as n, Patch as o, StoreOptions as p, ActionsTree as r, Plugin as s, Actions as t, Store as u };
48
+ export { SubscribeKey as _, GettersReturnType as a, Path as c, Signal as d, Store as f, Subscribe as g, StoreOptions as h, Getters as i, Plugin as l, StoreDefineOptions as m, ActionsOmitThisParameter as n, Op as o, StoreDefine as p, ActionsTree as r, Patch as s, Actions as t, PluginContext as u };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "valtio-define",
3
3
  "type": "module",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "description": "⚡quickly create a fully functional and robust Valtio factory",
6
6
  "author": "Hairyf <wwu710632@gmail.com>",
7
7
  "license": "MIT",
@@ -26,6 +26,10 @@
26
26
  "./plugins": "./dist/plugins/index.mjs",
27
27
  "./plugins/persist": "./dist/plugins/persist/index.mjs",
28
28
  "./plugins/persist/types": "./dist/plugins/persist/types.mjs",
29
+ "./plugins/signal": "./dist/plugins/signal/index.mjs",
30
+ "./plugins/signal/jsx-dev-runtime": "./dist/plugins/signal/jsx-dev-runtime.mjs",
31
+ "./plugins/signal/jsx-runtime": "./dist/plugins/signal/jsx-runtime.mjs",
32
+ "./plugins/signal/types": "./dist/plugins/signal/types.mjs",
29
33
  "./package.json": "./package.json"
30
34
  },
31
35
  "files": [
@@ -38,7 +42,9 @@
38
42
  "@hairy/utils": "^1.47.0",
39
43
  "destr": "^2.0.5",
40
44
  "structure-id": "^1.2.9",
41
- "valtio": "^2.3.0"
45
+ "valtio": "^2.3.0",
46
+ "valtio-reactive": "^0.2.0",
47
+ "valtio-signal": "^0.6.0"
42
48
  },
43
49
  "devDependencies": {
44
50
  "@antfu/eslint-config": "^7.6.1",
package/README.OLD.md DELETED
@@ -1,391 +0,0 @@
1
- # valtio-define
2
-
3
- [![npm version][npm-version-src]][npm-version-href]
4
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
- [![bundle][bundle-src]][bundle-href]
6
- [![JSDocs][jsdocs-src]][jsdocs-href]
7
- [![coverage][coverage-src]][coverage-href]
8
- [![License][license-src]][license-href]
9
-
10
- ⚡ Quickly create a fully functional and robust [Valtio](https://valtio.dev) factory
11
-
12
- ## Installation
13
-
14
- ```bash
15
- pnpm add valtio-define
16
- ```
17
-
18
- ## Usage
19
-
20
- ### Basic Store
21
-
22
- Create a reactive store with state and actions. The store provides a simple and intuitive API for managing state in React applications, built on top of Valtio.
23
-
24
- ```tsx
25
- import { defineStore, useStore } from 'valtio-define'
26
-
27
- const store = defineStore({
28
- state: () => ({ count: 0 }),
29
- actions: {
30
- increment() {
31
- this.count++
32
- },
33
- },
34
- })
35
-
36
- function Counter() {
37
- const { count } = useStore(store)
38
- return (
39
- <div>
40
- <button onClick={store.increment}>Increment</button>
41
- <div>{count}</div>
42
- </div>
43
- )
44
- }
45
- ```
46
-
47
- ### With Getters
48
-
49
- Getters are computed properties that automatically update when their dependencies change. They provide a clean way to derive state without manually tracking dependencies.
50
-
51
- ```tsx
52
- const store = defineStore({
53
- state: () => ({ count: 0 }),
54
- getters: {
55
- doubled() {
56
- return this.count * 2
57
- },
58
- },
59
- actions: {
60
- increment() {
61
- this.count++
62
- },
63
- },
64
- })
65
-
66
- function Counter() {
67
- const state = useStore(store)
68
- return (
69
- <div>
70
- <div>
71
- Count:
72
- {state.count}
73
- </div>
74
- <div>
75
- Doubled:
76
- {state.doubled}
77
- </div>
78
- <button onClick={store.increment}>Increment</button>
79
- </div>
80
- )
81
- }
82
- ```
83
-
84
- ### Persistence
85
-
86
- The persistence plugin allows you to persist store state to storage (e.g., localStorage).
87
-
88
- First, register the persist plugin:
89
-
90
- ```tsx
91
- import valtio from 'valtio-define'
92
- import { persist } from 'valtio-define/plugins'
93
-
94
- // Register the persist plugin globally
95
- valtio.use(persist())
96
- ```
97
-
98
- Then use it in your store:
99
-
100
- ```tsx
101
- import { defineStore } from 'valtio-define'
102
-
103
- const store = defineStore({
104
- state: () => ({ count: 0 }),
105
- actions: {
106
- increment() {
107
- this.count++
108
- },
109
- },
110
- persist: {
111
- key: 'my-store',
112
- storage: localStorage,
113
- paths: ['count'], // Only persist 'count', or omit to persist all state
114
- },
115
- })
116
- ```
117
-
118
- If `persist` is `true`, it will use `structure-id` to generate a unique key for the store automatically.
119
-
120
- ```tsx
121
- const store = defineStore({
122
- state: () => ({ count: 0 }),
123
- persist: true, // Auto-generates key using structure-id
124
- })
125
- ```
126
-
127
- You can pass `hydrate` when registering the plugin (default `true`). When `true`, state is hydrated from storage as soon as the store is created. Set `hydrate: false` when you need to avoid running persistence during server-side rendering, and manually mount persist in your App entry instead:
128
-
129
- ```tsx
130
- // Register with hydrate: false
131
- store.use(persist({ hydrate: false }))
132
-
133
- // In your App (client entry), after store is used:
134
- useEffect(() => store.$persist.mount(), [])
135
- ```
136
-
137
- ### Subscribe to Changes
138
-
139
- ```tsx
140
- const store = defineStore({
141
- state: () => ({ count: 0 }),
142
- actions: {
143
- increment() {
144
- this.count++
145
- },
146
- },
147
- })
148
-
149
- // Subscribe to state changes
150
- const unsubscribe = store.$subscribe((state) => {
151
- console.log('State changed:', state)
152
- })
153
-
154
- // Subscribe to specific key changes
155
- const unsubscribeKey = store.$subscribeKey('count', (value) => {
156
- console.log('Count changed:', value)
157
- })
158
- ```
159
-
160
- ### Patch State
161
-
162
- ```tsx
163
- // Patch with object
164
- store.$patch({ count: 10 })
165
-
166
- // Patch with function
167
- store.$patch((state) => {
168
- state.count += 5
169
- })
170
- ```
171
-
172
- ### Signal (JSX Component)
173
-
174
- ```tsx
175
- function App() {
176
- return (
177
- <div>
178
- Count:
179
- {' '}
180
- {store.$signal(state => state.count)}
181
- </div>
182
- )
183
- }
184
- ```
185
-
186
- ### Store to State Hooks
187
-
188
- Convert store state to React hooks similar to `useState`. This provides a more React-idiomatic way to access and update store state.
189
-
190
- #### `storeToState(store, key)`
191
-
192
- Returns a tuple `[state, setter]` for a single store key, similar to React's `useState`.
193
-
194
- ```tsx
195
- import { defineStore, storeToState } from 'valtio-define'
196
-
197
- const store = defineStore({
198
- state: { count: 0, name: 'test' },
199
- })
200
-
201
- function Counter() {
202
- const [count, setCount] = storeToState(store, 'count')
203
- const [name, setName] = storeToState(store, 'name')
204
-
205
- return (
206
- <div>
207
- <button onClick={() => setCount(count + 1)}>Increment</button>
208
- <button onClick={() => setCount(prev => prev + 1)}>Increment (functional)</button>
209
- <div>
210
- Count:
211
- {count}
212
- </div>
213
- <div>
214
- Name:
215
- {name}
216
- </div>
217
- </div>
218
- )
219
- }
220
- ```
221
-
222
- **Parameters:**
223
- - `store`: Store instance created by `defineStore`
224
- - `key`: Key of the state property to access
225
-
226
- **Returns:** `[state, setter]` tuple where:
227
- - `state`: Current value of the state property
228
- - `setter`: Function to update the state (accepts value or updater function)
229
-
230
- #### `storeToStates(store)`
231
-
232
- Returns an object with all store keys mapped to `[state, setter]` tuples.
233
-
234
- ```tsx
235
- function Component() {
236
- const {
237
- count: [count, setCount],
238
- name: [name, setName],
239
- user: [user, setUser],
240
- } = storeToStates(store)
241
-
242
- return (
243
- <div>
244
- <button onClick={() => setCount(count + 1)}>Increment</button>
245
- <div>
246
- Count:
247
- {count}
248
- </div>
249
- <div>
250
- Name:
251
- {name}
252
- </div>
253
- <div>
254
- User:
255
- {user.name}
256
- </div>
257
- </div>
258
- )
259
- }
260
- ```
261
-
262
- **Parameters:**
263
- - `store`: Store instance created by `defineStore`
264
-
265
- **Returns:** Object where each key maps to a `[state, setter]` tuple
266
-
267
- ## API
268
-
269
- ### `defineStore(store)`
270
-
271
- Creates a store with state, actions, and getters.
272
-
273
- **Parameters:**
274
- - `store.state`: Initial state object or factory function
275
- - `store.actions`: Object containing action methods
276
- - `store.getters`: Object containing getter methods
277
- - `store.persist`: Persistence plugin configuration (boolean or object) - see [Persistence Plugin](#persistence)
278
-
279
- **Returns:** Store instance with reactive state and actions
280
-
281
- ### `useStore(store)`
282
-
283
- React hook that returns a snapshot of the store state.
284
-
285
- **Parameters:**
286
- - `store`: Store instance created by `defineStore`
287
-
288
- **Returns:** Snapshot of the store state
289
-
290
- ### `storeToState(store, key)`
291
-
292
- React hook that returns a `[state, setter]` tuple for a single store key, similar to React's `useState`.
293
-
294
- **Parameters:**
295
- - `store`: Store instance created by `defineStore`
296
- - `key`: Key of the state property to access
297
-
298
- **Returns:** `[state, setter]` tuple where:
299
- - `state`: Current value of the state property
300
- - `setter`: Function to update the state (accepts value or updater function like `setState(value)` or `setState(prev => newValue)`)
301
-
302
- ### `storeToStates(store)`
303
-
304
- React hook that returns an object with all store keys mapped to `[state, setter]` tuples.
305
-
306
- **Parameters:**
307
- - `store`: Store instance created by `defineStore`
308
-
309
- **Returns:** Object where each key maps to a `[state, setter]` tuple, preserving the correct type for each property
310
-
311
- ### Plugins
312
-
313
- Plugins allow you to extend store functionality. You can use plugins globally or per-store.
314
-
315
- #### Global Plugin Registration
316
-
317
- ```tsx
318
- import valtio from 'valtio-define'
319
- import { persist } from 'valtio-define/plugins'
320
-
321
- // Register plugin globally - applies to all stores
322
- valtio.use(persist())
323
- ```
324
-
325
- #### Per-Store Plugin Registration
326
-
327
- ```tsx
328
- import { defineStore } from 'valtio-define'
329
- import { persist } from 'valtio-define/plugins'
330
-
331
- const store = defineStore({
332
- state: () => ({ count: 0 }),
333
- })
334
-
335
- // Register plugin for this specific store
336
- store.use(persist())
337
- ```
338
-
339
- #### Creating Custom Plugins
340
-
341
- ```tsx
342
- import type { Plugin } from 'valtio-define'
343
-
344
- function myPlugin() {
345
- ({ store, options }: PluginContext) => {
346
- // Access store methods
347
- store.$subscribe((state) => {
348
- console.log('State changed:', state)
349
- })
350
-
351
- // Access store options
352
- if (options.someOption) {
353
- // Do something
354
- }
355
- }
356
- }
357
-
358
- declare module 'valtio-define' {
359
- export interface StoreDefine<S extends object, A extends ActionsTree, G extends Getters<any>> {
360
- myPlugin?: {
361
- someOption?: boolean
362
- }
363
- }
364
- }
365
-
366
- // Use the plugin
367
- use(myPlugin())
368
- ```
369
-
370
- **Plugin Context:**
371
- - `context.store`: The store instance with all methods (`$state`, `$patch`, `$subscribe`, etc.)
372
- - `context.options`: The original store definition options
373
-
374
- ## License
375
-
376
- [MIT](./LICENSE) License © [Hairyf](https://github.com/hairyf)
377
-
378
- <!-- Badges -->
379
-
380
- [npm-version-src]: https://img.shields.io/npm/v/valtio-define?style=flat&colorA=080f12&colorB=1fa669
381
- [npm-version-href]: https://npmjs.com/package/valtio-define
382
- [npm-downloads-src]: https://img.shields.io/npm/dm/valtio-define?style=flat&colorA=080f12&colorB=1fa669
383
- [npm-downloads-href]: https://npmjs.com/package/valtio-define
384
- [bundle-src]: https://img.shields.io/bundlephobia/minzip/valtio-define?style=flat&colorA=080f12&colorB=1fa669&label=minzip
385
- [bundle-href]: https://bundlephobia.com/result?p=valtio-define
386
- [license-src]: https://img.shields.io/github/license/hairyf/valtio-define.svg?style=flat&colorA=080f12&colorB=1fa669
387
- [license-href]: https://github.com/hairyf/valtio-define/blob/main/LICENSE
388
- [jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
389
- [jsdocs-href]: https://www.jsdocs.io/package/valtio-define
390
- [coverage-src]: https://codecov.io/gh/hairyf/valtio-define/graph/badge.svg?token=PG5YIEEEHJ
391
- [coverage-href]: https://codecov.io/gh/hairyf/valtio-define