### Install mvc-kit using npm Source: https://github.com/thermsio/mvc-kit/blob/master/README.md This command installs the mvc-kit library using npm. Ensure you have Node.js and npm installed on your system. ```bash npm install mvc-kit ``` -------------------------------- ### ViewModel Initialization vs. Component Loading (TSX) Source: https://github.com/thermsio/mvc-kit/blob/master/BEST_PRACTICES.md Demonstrates the recommended approach for initializing ViewModels. The 'Good' example shows the ViewModel handling its own initialization via `onInit()`, while the 'Bad' example illustrates a less efficient pattern where the component directly orchestrates loading using `useEffect`. ```tsx // ✗ Bad: component orchestrates loading function UsersPage() { const [state, vm] = useLocal(UsersViewModel, { ... }); useEffect(() => { vm.load(); }, []); return
...
; } // ✓ Good: ViewModel handles its own initialization via onInit() function UsersPage() { const [state, vm] = useLocal(UsersViewModel, { ... }); return
...
; } ``` -------------------------------- ### Async Initialization of a Service (TypeScript) Source: https://github.com/thermsio/mvc-kit/blob/master/src/Service.md Illustrates how a Service can perform asynchronous operations during initialization by overriding the `onInit` hook. This example fetches configuration data before the service is ready to be used. ```typescript class ConfigService extends Service { private config: AppConfig | null = null; protected async onInit() { const res = await fetch('/api/config'); this.config = await res.json(); } getConfig(): AppConfig { return this.config!; } } const service = new ConfigService(); await service.init(); ``` -------------------------------- ### Implement Controller Lifecycle Hooks - TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/src/Controller.md Provides examples of overriding the protected `onInit()` and `onDispose()` methods. `onInit()` is used for setup tasks like initiating subscriptions or starting timers upon controller initialization, while `onDispose()` is for specific teardown logic after the controller has been disposed. ```typescript // Example for onInit (covered in other snippets) // Example for onDispose: protected onDispose() { // Perform specific cleanup actions here } ``` -------------------------------- ### Implement Lifecycle Hooks (TypeScript) Source: https://github.com/thermsio/mvc-kit/blob/master/src/Channel.md Demonstrates how to implement optional `onInit()` and `onDispose()` lifecycle hooks within a `ChatChannel` subclass. These methods allow for custom setup during initialization and final cleanup during disposal. ```typescript class ChatChannel extends Channel { protected onInit() { // Set up external subscriptions, start timers, etc. } protected onDispose() { // Final cleanup after everything else is torn down } } ``` -------------------------------- ### Connect Channel and Handle Messages (TypeScript) Source: https://github.com/thermsio/mvc-kit/blob/master/src/Channel.md An example within a ViewModel showing how to initialize a channel, connect to it, and set up a listener to handle incoming messages. This demonstrates a typical usage pattern for interacting with the channel in an application. ```typescript // In a ViewModel using a channel protected onInit() { this.channel.on('message', (msg) => { this.set({ messages: [...this.state.messages, msg] }); }); this.channel.connect(); } ``` -------------------------------- ### React Hook: useInstance Signature and Usage Example Source: https://github.com/thermsio/mvc-kit/blob/master/src/react/use-instance.md Provides the TypeScript signature for the `useInstance` hook and a basic example of its usage in a child component to display state from a parent ViewModel. ```typescript import { Subscribable } from 'your-library'; function useInstance(subscribable: Subscribable): Readonly; // Example Usage: // function ChildDisplay({ vm }: { vm: ParentViewModel }) { // const state = useInstance(vm); // return

{state.count}

; // } ``` -------------------------------- ### ViewModel State Management Example Source: https://github.com/thermsio/mvc-kit/blob/master/CLAUDE.md This example demonstrates the core concepts of the ViewModel class in mvc-kit, including state management, computed getters, and async tracking. It highlights how state is held internally and accessed via a getter, with derived values computed on the fly. ```typescript // Example demonstrating ViewModel internals (conceptual) class MyViewModel extends ViewModel<{ count: number; }> { constructor() { super({ count: 0 }); } // Computed getter get doubledCount() { return this.state.count * 2; } // Async method with tracking async incrementAsync() { await new Promise(resolve => setTimeout(resolve, 100)); this.set(state => ({ count: state.count + 1 })); } // Accessing async state get incrementing() { return this.async.incrementAsync; } } // Usage within a React component (conceptual) // const vm = useLocal(MyViewModel); // console.log(vm.doubledCount); // vm.incrementAsync(); // console.log(vm.incrementing.loading); ``` -------------------------------- ### Service Class Example in TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/README.md Demonstrates the Service class, a non-reactive infrastructure service typically used as a singleton. The example shows making an asynchronous API call with automatic cancellation if the service is disposed. ```typescript class ApiService extends Service { async fetchUser(id: string) { // this.disposeSignal auto-cancels if the service is disposed const res = await fetch(`/api/users/${id}`, { signal: this.disposeSignal }); return res.json(); } } ``` -------------------------------- ### Basic REST Service Implementation (TypeScript) Source: https://github.com/thermsio/mvc-kit/blob/master/src/Service.md Provides an example of a `LocationService` implementing basic RESTful operations (getAll, create, delete). It utilizes `fetch` and `HttpError` for handling responses and errors, and accepts an `AbortSignal` for cancellation. ```typescript class LocationService extends Service { async getAll(signal?: AbortSignal): Promise { const res = await fetch('/api/locations', { signal }); if (!res.ok) throw new HttpError(res.status, res.statusText); return res.json(); } async create(data: Omit, signal?: AbortSignal): Promise { const res = await fetch('/api/locations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), signal, }); if (!res.ok) throw new HttpError(res.status, res.statusText); return res.json(); } async delete(id: string, signal?: AbortSignal): Promise { const res = await fetch(`/api/locations/${id}`, { method: 'DELETE', signal }); if (!res.ok) throw new HttpError(res.status, res.statusText); } } ``` -------------------------------- ### Controller Class Example in TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/README.md Illustrates the Controller class for orchestrating complex logic. It's stateless and component-scoped, with automatic disposal. The example shows setting up subscriptions and handling asynchronous operations with disposal cancellation. ```typescript class CheckoutController extends Controller { constructor( private cart: CartViewModel, private api: ApiService ) { super(); } // Called once after init() — set up subscriptions, wire dependencies protected onInit() { // subscribeTo registers auto-cleanup — no manual tracking needed this.subscribeTo(this.cart, () => this.onCartChanged()); } async submit() { const items = this.cart.state.items; // this.disposeSignal auto-cancels the request if the controller is disposed mid-flight await this.api.checkout(items, { signal: this.disposeSignal }); this.cart.clear(); } } ``` -------------------------------- ### Pre-populating Singletons for Test Setup in TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/src/singleton.md Illustrates how to pre-populate shared singleton instances, like collections, before constructing a ViewModel in tests. This ensures that the ViewModel, when it resolves the same singleton, receives the pre-populated data, facilitating isolated and predictable test scenarios. ```typescript beforeEach(() => teardownAll()); test('filtered getter applies search', () => { const collection = singleton(UsersCollection); collection.reset([ { id: '1', firstName: 'Alice', status: 'on_duty' }, { id: '2', firstName: 'Bob', status: 'off_duty' }, ]); const vm = new UsersViewModel({ items: [], search: '' }); vm.init(); // onInit subscribes to the same collection instance expect(vm.state.items).toHaveLength(2); vm.dispose(); }); ``` -------------------------------- ### Example: Auto-Initialization of Singleton Instance (React) Source: https://github.com/thermsio/mvc-kit/blob/master/src/react/use-singleton.md Illustrates the auto-initialization behavior of `useSingleton`. When a singleton instance implements an `init` method, it is called automatically on mount. This initialization logic runs only once, even if multiple components mount using the same singleton. ```javascript class InitVM extends ViewModel { protected onInit() { console.log('runs once, even with 5 components'); } } // All five components share one instance; onInit runs once function App() { return ( <> ); } ``` ```javascript class ConfigService extends Service { protected onInit() { // Runs once on first mount } } function Comp() { const service = useSingleton(ConfigService); // service.init() called automatically } ``` -------------------------------- ### ViewModel Class Example in TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/README.md Illustrates advanced usage of the ViewModel class, including state updates with functions, lifecycle hooks (onInit, onSet, onDispose), and asynchronous operations with automatic disposal handling. ```typescript class TodosViewModel extends ViewModel<{ items: string[] }> { addItem(item: string) { this.set(prev => ({ items: [...prev.items, item] })); } // Called once after init() — use for subscriptions, data fetching, etc. protected onInit() { this.loadItems(); } // Called after every state change protected onSet(prev: Readonly<{ items: string[] }>, next: Readonly<{ items: string[] }>) { console.log('Items changed:', prev.items.length, '→', next.items.length); } protected onDispose() { // Cleanup logic } // After init(), async methods are automatically tracked: // vm.async.loadItems → { loading: boolean, error: string | null } async loadItems() { // this.disposeSignal is automatically aborted on dispose — fetch() will throw AbortError const res = await fetch('/api/items', { signal: this.disposeSignal }); const items = await res.json(); this.set({ items }); } } ``` -------------------------------- ### Test ViewModel State and Filtered Results Source: https://github.com/thermsio/mvc-kit/blob/master/BEST_PRACTICES.md Demonstrates how to test a ViewModel by constructing it, initializing it, and asserting its state and derived properties like 'filtered'. This example shows setting search parameters and verifying the filtered results. ```typescript import { singleton, teardownAll } from 'mvc-kit'; beforeEach(() => teardownAll()); test('filtered getter applies search', () => { const collection = singleton(UsersCollection); collection.reset([ { id: '1', firstName: 'Alice', status: 'on_duty' }, { id: '2', firstName: 'Bob', status: 'off_duty' }, ]); const vm = new UsersViewModel({ items: [], search: '', roleFilter: 'all' }); vm.init(); expect(vm.state.items).toHaveLength(2); expect(vm.filtered).toHaveLength(2); vm.setSearch('alice'); expect(vm.filtered).toHaveLength(1); expect(vm.filtered[0].firstName).toBe('Alice'); vm.dispose(); }); ``` -------------------------------- ### Model Class Example in TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/README.md Shows how to use the Model class for reactive entities with built-in validation and dirty tracking. It includes methods for setting state, defining validation rules, and managing the committed state. ```typescript class UserModel extends Model<{ name: string; email: string }> { setName(name: string) { this.set({ name }); } protected validate(state: { name: string; email: string }) { const errors: Partial> = {}; if (!state.name) errors.name = 'Name is required'; if (!state.email.includes('@')) errors.email = 'Invalid email'; return errors; } } const user = new UserModel({ name: '', email: '' }); console.log(user.valid); // false console.log(user.errors); // { name: 'Name is required', email: 'Invalid email' } user.setName('John'); console.log(user.dirty); // true (differs from committed state) user.commit(); // Mark current state as baseline user.rollback(); // Revert to committed state ``` -------------------------------- ### Get Singleton Instance with useSingleton Source: https://github.com/thermsio/mvc-kit/blob/master/README.md The `useSingleton` hook retrieves a singleton instance managed by a registry. It automatically calls the `init()` method after the component mounts. This is useful for both subscribable singletons (like ViewModels) and service singletons. ```tsx // Subscribable singleton function UserProfile() { const [state, vm] = useSingleton(UserViewModel); return
{state.name}
; } // Service singleton function Dashboard() { const api = useSingleton(ApiService); // ... } ``` -------------------------------- ### Quick Start: Counter ViewModel in TypeScript Source: https://github.com/thermsio/mvc-kit/blob/master/README.md Demonstrates a basic CounterViewModel using mvc-kit. It extends the ViewModel class to manage a 'count' state and provides an 'increment' method to update it. Subscriptions allow reacting to state changes. ```typescript import { ViewModel } from 'mvc-kit'; interface CounterState { count: number; } class CounterViewModel extends ViewModel { increment() { this.set({ count: this.state.count + 1 }); } } const counter = new CounterViewModel({ count: 0 }); counter.subscribe((state, prev) => console.log(state.count)); counter.increment(); // logs: 1 ``` -------------------------------- ### ViewModel: Reactive State Container Example in TypeScript Source: https://context7.com/thermsio/mvc-kit/llms.txt Demonstrates the usage of the ViewModel class from mvc-kit for reactive state management. It covers state initialization, computed properties, lifecycle methods (onInit, onSet, onDispose), event emission, and automatic tracking of asynchronous operations. Dependencies include the 'mvc-kit' library. ```typescript import { ViewModel } from 'mvc-kit'; interface TodoState { items: string[]; filter: 'all' | 'active' | 'completed'; } interface TodoEvents { 'item:added': { text: string }; 'item:removed': { id: string }; } class TodoViewModel extends ViewModel { // Computed getter - automatically memoized after init() get filteredItems(): string[] { return this.state.filter === 'all' ? this.state.items : this.state.items.filter(item => item.length > 0); } // Called once after init() protected onInit() { this.loadItems(); } // Called after every state change protected onSet(prev: Readonly, next: Readonly) { console.log('State changed:', prev.items.length, '→', next.items.length); } // Cleanup logic protected onDispose() { console.log('ViewModel disposed'); } addItem(text: string) { this.set(prev => ({ items: [...prev.items, text] })); this.emit('item:added', { text }); // Type-safe event emission } setFilter(filter: TodoState['filter']) { this.set({ filter }); } // Async methods are automatically tracked after init() async loadItems() { // disposeSignal auto-aborts on dispose - fetch throws AbortError const res = await fetch('/api/items', { signal: this.disposeSignal }); const items = await res.json(); this.set({ items }); } async saveItem(text: string) { const res = await fetch('/api/items', { method: 'POST', body: JSON.stringify({ text }), signal: this.disposeSignal, }); return res.json(); } } // Usage const vm = new TodoViewModel({ items: [], filter: 'all' }); vm.init(); // Must call init() to activate auto-tracking // Subscribe to state changes const unsubscribe = vm.subscribe((state, prev) => { console.log('New state:', state); }); // Subscribe to events vm.events.on('item:added', ({ text }) => console.log('Added:', text)); // Use async tracking vm.loadItems(); console.log(vm.async.loadItems); // { loading: true, error: null, errorCode: null } await vm.loadItems(); console.log(vm.async.loadItems); // { loading: false, error: null, errorCode: null } // Cleanup vm.dispose(); ``` -------------------------------- ### Use Event and Emit Hooks for EventBus and ViewModel Communication in React Source: https://context7.com/thermsio/mvc-kit/llms.txt Demonstrates how to use `useEvent` to subscribe to events from an `EventBus` or `ViewModel` with automatic cleanup, and `useEmit` to get a stable reference to an emit function. This is useful for managing global state and inter-component communication. ```tsx import { useEvent, useEmit, useLocal } from 'mvc-kit/react'; import { EventBus, ViewModel } from 'mvc-kit'; import { useState } from 'react'; interface AppEvents { 'toast': { message: string; type: 'success' | 'error' | 'info' }; 'modal:open': { id: string }; 'modal:close': void; } // Global event bus const appEvents = new EventBus(); // Toast notifications using useEvent function ToastContainer() { const [toasts, setToasts] = useState>([]); useEvent(appEvents, 'toast', ({ message, type }) => { const id = Date.now(); setToasts(prev => [...prev, { id, message, type }]); setTimeout(() => { setToasts(prev => prev.filter(t => t.id !== id)); }, 3000); }); return (
{toasts.map(toast => (
{toast.message}
))}
); } // Emit events using useEmit function ActionButton() { const emit = useEmit(appEvents); const handleSuccess = () => { emit('toast', { message: 'Action completed!', type: 'success' }); }; const handleError = () => { emit('toast', { message: 'Something went wrong', type: 'error' }); }; return (
); } // Subscribe to ViewModel events interface SaveEvents { 'saved': { id: string }; 'error': { message: string }; } interface DocState { title: string; content: string; } class DocumentViewModel extends ViewModel { async save() { try { const res = await fetch('/api/documents', { method: 'POST', body: JSON.stringify(this.state), signal: this.disposeSignal, }); const { id } = await res.json(); this.emit('saved', { id }); } catch (e) { this.emit('error', { message: 'Save failed' }); } } setTitle(title: string) { this.set({ title }); } setContent(content: string) { this.set({ content }); } } function DocumentEditor() { const [state, vm] = useLocal(DocumentViewModel, { title: '', content: '' }); // Subscribe to ViewModel events useEvent(vm, 'saved', ({ id }) => { console.log('Document saved with ID:', id); appEvents.emit('toast', { message: 'Document saved!', type: 'success' }); }); useEvent(vm, 'error', ({ message }) => { appEvents.emit('toast', { message, type: 'error' }); }); return (
vm.setTitle(e.target.value)} />