State Management
On this page 16
stx provides powerful state management capabilities to help you manage application data effectively. This guide covers both local component state and global application state.
Component State
Basic State Management
Use reactive state in components:
@component('')
let count = 0
function increment() {
count++
}
function decrement() {
count--
}
<divclass="">
<button@click="decrement"></button>
<span>{{ count }}</span>
<button@click="increment"></button>
</div>
Computed Properties
Create derived state:
@component('')
interface Todo {
id: number
text: string
completed: boolean
}
const todos = ref<Todo[]>([])
const completedTodos = computed(() =>
todos.value.filter(todo => todo.completed)
)
const incompleteTodos = computed(() =>
todos.value.filter(todo => !todo.completed)
)
<divclass="">
<h3>Completed ({{ completedTodos.length }})</h3>
<todo-items:items="completedTodos" />
<h3>Incomplete ({{ incompleteTodos.length }})</h3>
<todo-items:items="incompleteTodos" />
</div>
Watchers
React to state changes:
@component('')
interface User {
id: number
name: string
preferences: Record<stringany>
}
const user = ref<Usernull>(null)
watch(() => user.value?.preferences, (newPrefs, oldPrefs) => {
if (newPrefs !== oldPrefs) {
savePreferences(newPrefs)
}
}, { deep: true })
<divclass="">
@if(user)
<user-preferences:preferences="user" />
</div>
Global State
Store Setup
Create a global store:
// store/index.ts
import { createStore } from '@stacksjs/stx/store'
interface State {
user: User | null
theme: 'light' | 'dark'
notifications: Notification[]
}
interface Actions {
setUser(user: User | null): void
toggleTheme(): void
addNotification(notification: Notification): void
}
export const store = createStore<State, Actions>({
state: {
user: null,
theme: 'light',
notifications: []
},
actions: {
setUser(state, user) {
state.user = user
},
toggleTheme(state) {
state.theme = state.theme === 'light' ? 'dark' : 'light'
},
addNotification(state, notification) {
state.notifications.push(notification)
}
}
})
Using the Store
Access store state in components:
@component('')
import { store } from '@/store'
const { user, theme } = store.state
const { toggleTheme } = store.actions
<headerx-class="">
@if(user)
<user-menu:user="user" />
<login-button />
<button@click="toggleTheme">
Toggle Theme
</button>
</header>
Store Modules
Organize state with modules:
// store/modules/auth.ts
export const auth = {
state: {
user: null,
token: null
},
actions: {
async login(state, credentials) {
const response = await api.login(credentials)
state.user = response.user
state.token = response.token
},
logout(state) {
state.user = null
state.token = null
}
}
}
// store/index.ts
import { auth } from './modules/auth'
export const store = createStore({
modules: {
auth
}
})
State Persistence
Local Storage
Persist state to local storage:
import { createPersistedStore } from '@stacksjs/stx/store'
export const store = createPersistedStore({
state: {
theme: 'light',
settings: {}
},
persistence: {
key: 'app-state',
paths: ['theme', 'settings'],
storage: localStorage
}
})
Custom Storage
Implement custom persistence:
const customStorage = {
async get(key: string) {
const value = await api.getState(key)
return JSON.parse(value)
},
async set(key: string, value: any) {
await api.setState(key, JSON.stringify(value))
}
}
export const store = createPersistedStore({
// ... store config
persistence: {
storage: customStorage
}
})
State Management Patterns
Component Communication
Share state between components:
@component('')
const sharedState = ref({
value: 0
})
<div>
<child-a:state="sharedState" />
<child-b:state="sharedState" />
</div>
@component('')
const props = defineProps<{
state: { value: number }
}>()
function increment() {
props.state.value++
}
<button@click="increment">
Increment from A
</button>
State Composition
Compose multiple state sources:
function useUserState() {
const user = ref(null)
const loading = ref(false)
async function fetchUser(id: number) {
loading.value = true
try {
user.value = await api.getUser(id)
} finally {
loading.value = false
}
}
return {
user,
loading,
fetchUser
}
}
function usePermissions(user) {
const can = (action: string) => {
return user.value?.permissions.includes(action)
}
return { can }
}
// Usage in component
@component('UserDashboard')
@ts
const { user, loading, fetchUser } = useUserState()
const { can } = usePermissions(user)
@endts
<div>
@if(loading)
<loading-spinner />
@elseif(user)
@if(can('view-dashboard'))
<dashboard-content />
@endif
@endif
</div>
@endcomponent
Best Practices
-
State Organization
- Keep state minimal
- Use computed properties
- Split into modules
- Document state shape
-
Performance
- Use shallow refs when possible
- Avoid deep watching large objects
- Batch updates
- Optimize rerenders
-
State Access
- Use composition
- Implement access control
- Handle loading states
- Manage side effects
-
Persistence
- Choose appropriate storage
- Handle errors
- Implement migrations
- Secure sensitive data
Next Steps
- Learn about Testing
- Explore Performance
- Check out Security
- Review Deployment