valtio-define
v1.4.0
Published
⚡quickly create a fully functional and robust Valtio factory
Downloads
797
Maintainers
Readme
⚡️ valtio-define is a lightweight factory for creating fully functional, robust Valtio stores. It simplifies state management in React by providing a structured API for state, actions, and computed getters.
📦 Installation
pnpm add valtio valtio-defineYou can run npx skills add hairyf/valtio-define --skill valtio-define -y to install the skills!
Remember to ask your owner which Agents need to be supported (using the --agent parameter of skills) to avoid installing too many default agents directories.
🚀 Quick Start
Basic Store
defineStore allows you to encapsulate state and logic in one place. Use useStore to consume the reactive state in your components.
import { defineStore, useStore } from 'valtio-define'
const store = defineStore({
state: () => ({ count: 0 }),
actions: {
increment() {
// 'this' refers to the reactive state
this.count++
},
},
getters: {
doubled() {
return this.count * 2
},
},
})
function Counter() {
const { count, doubled } = useStore(store)
return (
<div>
<button onClick={store.increment}>Increment</button>
<div>Count: {count}</div>
<div>Doubled: {doubled}</div>
</div>
)
}🛠 Advanced Features
The Power of this
Inside actions and getters, this provides full access to the store's state, other actions, and other getters. This type safety across the entire store.
const store = defineStore({
state: () => ({
count: 0,
}),
actions: {
// Autocompletion and typings for the whole store ✨
currentDoubledOne() {
return this.doublePlusOne
},
},
getters: {
doubled() {
return this.count * 2
},
// Note: The return type **must** be explicitly set for complex getters
doublePlusOne() {
// Access other getters via 'this'
return this.doubled + 1
},
},
})Persistence
Save and restore your store state using the persist plugin.
Global Registration:
import valtio from 'valtio-define' import { persist } from 'valtio-define/plugins/persist' valtio.use(persist())Store Configuration:
const store = defineStore({ state: () => ({ count: 0 }), persist: { key: 'my-app-storage', storage: localStorage, paths: ['count'], // Optional: Persist specific keys only }, })Tip: If you set
persist: true, a unique key is automatically generated usingstructure-id.
Manual Hydration (SSR Friendly)
To avoid hydration mismatches during Server-Side Rendering, disable automatic hydration and rehydrate it in a useEffect.
// Register with hydrate disabled
store.use(persist({ hydrate: false }))
// In your Client Entry / App Root
useEffect(() => {
store.$persist.rehydrate()
}, [])React-Idiomatic State Hooks
If you prefer the useState syntax, use storeToState or storeToStates. These return [state, setter] tuples.
function Profile() {
// Access a single key
const [name, setName] = storeToState(store, 'name')
// Access all keys as hooks
const {
count: [count, setCount]
} = storeToStates(store)
return <input value={name} onChange={e => setName(e.target.value)} />
}Controlled inputs may lose caret position
Ref: https://github.com/pmndrs/valtio/issues/270
This happens because Valtio batches state updates causing React to re-render after the input event. React resets the DOM value and loses the caret position.
Use { sync: true } to update synchronously and preserve the caret:
function Input() {
const { text } = useStore(store, { sync: true })
// const [text, setText] = storeToState(store, 'text', { sync: true })
return (
<input
onChange={e => store.text = e.target.value}
value={text}
/>
)
}🛰 Store API
Every store instance created with defineStore includes built-in utility methods:
$patch(obj | fn): Bulk update the state.$subscribe(callback): Watch the entire store for changes.$subscribeKey(key, callback): Watch a specific property.$signal(selector): Render reactive values inline in JSX (works best with the Signal plugin +@jsxImportSource).
📡 Signal
Register the Signal plugin globally:
import valtio from 'valtio-define'
import { signal } from 'valtio-define/plugins/signal'
valtio.use(signal())Add jsxImportSource at the beginning of your .tsx file:
/** @jsxImportSource valtio-define/plugins/signal */
function App() {
return <div>{store.$signal(state => state.count)}</div>
}🔌 Plugins
Global vs. Per-Store
Plugins can be applied to all stores or restricted to a single instance.
import valtio, { defineStore } from 'valtio-define'
// Global
valtio.use(myPlugin())
// Local
const store = defineStore({ /* ... */ })
store.use(myPlugin())
// Local options
const store = defineStore({
use: [myPlugin()],
})Creating a Custom Plugin
Extend functionality by accessing the store instance and options through the plugin context.
import type { Plugin } from 'valtio-define'
import valtio from 'valtio-define'
function loggerPlugin(): Plugin {
return ({ store, options }) => {
store.$subscribe((state) => {
console.log('Update:', state)
})
}
}
valtio.use(loggerPlugin())
declare module 'valtio-define' {
export interface StoreDefineOptions<S extends object> {
$myPlugin?: {
someOption?: boolean
}
}
}