Store
@praxisjs/store — class-based singleton stores with @Store and @UseStore. Define reactive state with @State, inject anywhere with @UseStore.
Store
Class-based singleton state management. Define a store once with @Store, inject it into any component with @UseStore. All components reading the same store stay in sync automatically.
npm install @praxisjs/storepnpm add @praxisjs/storeyarn add @praxisjs/storebun add @praxisjs/storeDefining a store with @Store()
Extends ReactiveStore to use @State, @Computed, and @DeepState on store fields. The class is instantiated once on first use and the same instance is shared everywhere.
import { Store, ReactiveStore } from '@praxisjs/store'
import { State, Computed } from '@praxisjs/decorators'
@Store()
class CartStore extends ReactiveStore {
@State() items: Product[] = []
@State() discount = 0
@Computed()
get total() {
return this.items.reduce((sum, i) => sum + i.price, 0) * (1 - this.discount)
}
addItem(product: Product) {
this.items = [...this.items, product]
}
removeItem(id: string) {
this.items = this.items.filter(i => i.id !== id)
}
applyDiscount(rate: number) {
this.discount = rate
}
}Why extend ReactiveStore?
ReactiveStore provides the internal state tracking that @State and @DeepState require on non-component classes. It also lets TypeScript validate that reactive decorators are used only on classes designed to hold reactive state.
Injecting a store with @UseStore(StoreClass)
Injects the singleton store instance into a component field. The store is created on first access — subsequent injections return the same instance.
import { UseStore } from '@praxisjs/store'
@Component()
class CartButton extends StatefulComponent {
@UseStore(CartStore) cart!: CartStore
render() {
return (
<button onClick={() => this.cart.addItem(product)}>
Add to cart ({() => this.cart.items.length})
</button>
)
}
}Any component that reads a reactive property from the store updates automatically when that property changes — no subscriptions, no context providers.
Sharing state across components
Multiple components can inject the same store. They all share the same instance and react to the same changes:
@Component()
class CartSummary extends StatefulComponent {
@UseStore(CartStore) cart!: CartStore
render() {
return (
<div>
<p>{() => this.cart.items.length} items</p>
<p>Total: ${() => this.cart.total.toFixed(2)}</p>
</div>
)
}
}
@Component()
class CheckoutPage extends StatefulComponent {
@UseStore(CartStore) cart!: CartStore
render() {
return (
<div>
{() => this.cart.items.map(item => <CartItem item={item} />)}
<button onClick={() => this.cart.applyDiscount(0.1)}>Apply 10% off</button>
</div>
)
}
}When any component calls cart.addItem(), both CartSummary and CheckoutPage update automatically — because they both read reactive properties from the shared store instance.
Auth store example
A common pattern — a store that holds authentication state and exposes derived values:
@Store()
class AuthStore extends ReactiveStore {
@State() user: User | null = null
@Computed()
get isLoggedIn() { return this.user !== null }
login(user: User) { this.user = user }
logout() { this.user = null }
}
// In any component:
@UseStore(AuthStore) auth!: AuthStore
render() {
return () => this.auth.isLoggedIn
? <UserMenu user={this.auth.user!} />
: <LoginButton />
}Router
@praxisjs/router — signal-based client-side router. Configure routes with @RouterConfig, define lazy routes with @Lazy, annotate pages with @Route, and inject reactive router state with @InjectRouter, @Params, @Query, @Location.
Dependency Injection
@praxisjs/di — decorator-based DI with @Injectable, @Inject, @InjectContainer, and @Scope for per-instance scoped containers.