--- url: /mobx-view-model/react/api/only-view-model.md --- # `` Component Component that creates an instance of a passed [`ViewModel`](/api/view-models/interface) class and renders nothing or the provided `children`.\ If `children` is a function, it receives the created model.\ `ViewModelSimple` is not supported here. ## Example ```tsx import { ViewModelBase, OnlyViewModel } from "mobx-view-model"; class TestVM extends ViewModelBase { foo = 100; } ``` --- --- url: /mobx-view-model/react/api/use-create-view-model.md --- # `useCreateViewModel` hook A hook that connects a [ViewModel](/api/view-models/overview) (or `ViewModelSimple`) to the React render tree, providing seamless MobX integration.\ It is used inside the [`withViewModel()`](/react/api/with-view-model) HOC. ## API Signature ```tsx function useCreateViewModel( ViewModelClass: Class, payload?: ViewModelPayload, config?: UseCreateViewModelConfig ): VM; ``` ## Usage ### 1. Basic Usage (Default Configuration) ```tsx import { observer } from "mobx-react-lite"; export const YourComponent = observer(() => { const model = useCreateViewModel(YourVM); }) ``` ### 2. Usage with payload ```tsx import { observer } from "mobx-react-lite"; export const YourComponent = observer(() => { const model = useCreateViewModel(YourVM, { userId: '1' }); }) ``` ### 3. Custom Configuration ```tsx import { observer } from "mobx-react-lite"; export const YourComponent = observer(() => { const model = useCreateViewModel(YourVM, {}, { vmConfig: {}, // vmConfig ctx: {}, // internal object used as cache key source inside this hook factory: (config) => new config.VM(config), // factory method for creating VM instances generateId, // custom fn for generating ids for VM instances id, // unique id if you need to create 1 instance of your VM anchors: [], // additional components for useViewModel lookup }); }) ``` ### Example: ```tsx import { ViewModelBase } from "mobx-view-model"; import { observer } from "mobx-react-lite"; import { observable, action } from "mobx"; class VM extends ViewModelBase { @observable accessor value = ''; @action setValue = (value: string) => { this.value = value; } } export const YourComponent = observer(() => { const model = useCreateViewModel(VM) return (
model.setValue(e.target.value)} />
) }) ``` --- --- url: /mobx-view-model/react/api/use-view-model.md --- # `useViewModel` hook A hook that provides access to an **already created** [ViewModel](/api/view-models/overview) instance within a React component. ::: tip If you need to **create** instance of [ViewModel](/api/view-models/overview)\ Please use the [`useCreateViewModel`](/react/api/use-create-view-model) hook or the [`withViewModel`](/react/api/with-view-model) HOC.\ ::: ## API Signature ```tsx function useViewModel(): VM function useViewModel(vmLookup: ViewModelLookup): VM ``` ## Usage ### 1. Basic Usage ::: tip Requires [`withViewModel()`](/react/api/with-view-model) HOC usage to access\ ::: Reference to the last created [ViewModel](/api/view-models/overview) instance based on the React tree.\ Use a generic type (`YourVM`) to define the return type of the [view model instance](/api/view-models/overview). ```tsx import { observer } from "mobx-react-lite"; export const YourComponent = observer(() => { const yourVM = useViewModel(); }); ``` ### 2. Precise search with [ViewModelLookup](/api/other/view-model-lookup) ::: tip Requires `ViewModelStore` This variant requires a connected [`ViewModelStore`](/api/view-model-store/overview) in your React application using the [``](/react/api/view-models-provider) HOC. ::: Use the [`vmLookup`](/api/other/view-model-lookup) argument to define a specific identifier for the returned [ViewModel](/api/view-models/interface) instance, and use the generic the same way as above. ```tsx import { observer } from "mobx-react-lite"; export const YourComponent = observer(() => { const yourVM = useViewModel('view-model-id'); }); ``` ### 3. Lookup by anchor component When using [anchors](/react/api/with-view-model#anchors) or [connect()](/react/api/with-view-model#connectanchor----method-on-returned-vmcomponent), pass the anchor component as lookup: ```tsx const Anchor = () => null; const MainView = withViewModel(VM, View, { anchors: [Anchor] }); const Consumer = observer(() => { const model = useViewModel(Anchor); // same VM as MainView receives return {model.id}; }); ``` ### 4. Lookup from lazy-loaded component With [react-simple-loadable](https://github.com/js2me/react-simple-loadable) or similar, use `connect()` to register the loadable as anchor. `PageLazy` is the loadable-wrapped `Page`; after `Page.connect(PageLazy)` the lazy chunk can access the VM via `useViewModel(PageLazy)`: ```tsx // page.lazy.tsx import { loadable } from 'react-simple-loadable'; export const PageLazy = loadable( () => import('./page').then((m) => m.Page), () =>
Loading...
, ); // page.tsx import { PageLazy } from './page.lazy'; export const Page = withViewModel(PageVM, ({ model }) => (
...
)); Page.connect(PageLazy); ``` ```tsx // Some child inside the lazy chunk — uses the same VM const model = useViewModel(PageLazy); ``` --- --- url: /mobx-view-model/api/view-models/view-models-config.md --- # `ViewModelsConfig` configuration object This configuration contains all options for the behavior of [`ViewModel`](/api/view-models/overview) instances. The package provides a **global object** with this configuration, but you can also change it for each [`ViewModel`](/api/view-models/overview) and [`ViewModelStore`](/api/view-model-store/overview) separately using the `vmConfig` field. ```ts import { viewModelsConfig, withViewModel, ViewModelStoreBase } from "mobx-view-model"; viewModelsConfig.comparePayload = false; import { withViewModel } from "mobx-view-model"; withViewModel(VM, { vmConfig: { comparePayload: false } })() new ViewModelStoreBase({ vmConfig: { comparePayload: false } }) ``` [Reference to source code](/src/config/global-config.ts#L9) ## Recommendations These are the recommended settings for the global configuration `viewModelsConfig`, which contain the most optimal values. ```ts import { viewModelsConfig, ViewModelStoreBase } from 'mobx-view-model'; viewModelsConfig.comparePayload = false; viewModelsConfig.payloadComputed = 'struct'; viewModelsConfig.payloadObservable = 'ref'; viewModelsConfig.observable.viewModels.useDecorators = true; //false viewModelsConfig.observable.viewModelStores.useDecorators = true; // false ``` ## `startViewTransitions` Controls view transitions for view model lifecycle moments.\ [MDN Reference](https://developer.mozilla.org/docs/Web/API/View_Transitions_API) #### Shape `startViewTransitions` is an object with these flags: * `mount` - start transition when the view mounts * `payloadChange` - start transition when the payload changes * `unmount` - start transition when the view unmounts In `ViewModelsRawConfig` you can also pass a boolean to toggle all flags at once. ::: warning\ This feature is experimental and not all browsers support it yet.\ ::: ## `comparePayload` Allows you to configure how payload should be compared * `'strict'` - structural equality ([`comparer.structural` from MobX](https://mobx.js.org/computeds.html#built-in-comparers)) * `'shallow'` - shallow equality * `false` - ***(default)***, **(recommended)** no comparison * `fn` - custom payload compare fn (e.g. [MobX comparer functions](https://mobx.js.org/computeds.html#built-in-comparers)) ## `payloadComputed` Allows you to configure `computed` statement of the payload * `'struct'` - ***(default)***, **(recommended)** [`computed.struct` from MobX](https://mobx.js.org/computeds.html#computed-struct) * `true` - [`computed` from MobX](https://mobx.js.org/computeds.html) * `fn` - [custom equality function](https://mobx.js.org/computeds.html#equals) for [`computed` from MobX](https://mobx.js.org/computeds.html) * `false` - do not wrap `payload` into `computed` MobX utility ## `payloadObservable` Indicates type of observable for `ViewModel` payload. * `'ref'` - ***(default)*** **(recommended)** [MobX ref observable](https://mobx.js.org/api.html#observableref) * `'deep'` - [MobX deep observable](https://mobx.js.org/api.html#observabledeep) * `'struct'` - [MobX struct observable](https://mobx.js.org/api.html#observablestruct) * `'shallow'` - [MobX shallow observable](https://mobx.js.org/api.html#observableshallow) * `false` - no observable wrapping ## `generateId()` Generates a unique identifier for a [`ViewModel`](/api/view-models/interface). ::: tip This property has default implementation [here](/src/utils/generate-vm-id.ts#L16) ::: #### Example *Using `crypto.randomUUID()` to generate view model ids* ```ts{3} import { viewModelsConfig } from "mobx-view-model"; viewModelsConfig.generateId = () => crypto.randomUUID(); ``` ## `factory()` Factory function for creating [`ViewModel`](/api/view-models/interface) instances. Can be helpful if you want to add some constructor arguments for your own [`ViewModel`](/api/view-models/interface) implementation ::: tip This property has default implementation [here](/src/config/global-config.ts#L25) ::: #### Example *Passing `RootStore` as first constructor parameter* ```ts{2,6} import { viewModelsConfig } from "mobx-view-model"; import { rootStore } from "@/shared/store"; viewModelsConfig.factory = (config) => { const { VM } = config; return new VM(rootStore, config); } ``` ## `fallbackComponent` A component that will be rendered while the view model is in a loading or processing state.\ This is useful for showing loading spinners, skeletons, or placeholder content. #### Example ```tsx viewModelsConfig.fallbackComponent = () => (
Loading...
); ``` ## `onMount` A lifecycle hook that is called when a view model is mounted.\ Useful for tracking component mounting, initializing external services, or setting up subscriptions. #### Example ```tsx viewModelsConfig.onMount = (viewModel) => { console.log(`ViewModel ${viewModel.id} mounted`); // Setup analytics tracking analytics.track('component_mounted', { id: viewModel.id }); }; ``` ## `onUnmount` A lifecycle hook that is called when a view model is unmounted.\ Useful for cleanup operations, removing subscriptions, or tracking component lifecycle. #### Example ```tsx viewModelsConfig.onUnmount = (viewModel) => { console.log(`ViewModel ${viewModel.id} unmounted`); // Cleanup subscriptions }; ``` ## `hooks` Internal event hooks for view model stores. ### `hooks.storeCreate` Called when a `ViewModelStore` instance is created.\ Useful for wiring external listeners or diagnostics. ## `processViewComponent` A higher-order function that processes and transforms the view component before it is rendered.\ This function enables component composition and modification at the ViewModel level, allowing for: * Wrapping components with additional functionality (error boundaries, providers, etc.) * Injecting props or context * Modifying component behavior * Adding global features like logging, analytics, or performance monitoring #### Example ```tsx viewModelsConfig.processViewComponent = (Component) => { return (props) => { return ( ) } } ``` ::: warning It works only for [`withViewModel` HOCs](/react/api/with-view-model)\ ::: ## `wrapViewsInObserver` Wrap View components in [`observer()` MobX HOC](https://mobx.js.org/api.html#observer)\ This property is enabled by default. You can turn off this behavior by setting `wrapViewsInObserver` to `false`.\ Example: ```tsx import { viewModelsConfig } from "mobx-view-model"; viewModelsConfig.wrapViewsInObserver = false; ``` ::: warning It works only for [`withViewModel` HOCs](/react/api/with-view-model)\ ::: ## `observable` This is a large configuration object for all base implementations in `mobx-view-model`, like `ViewModelBase` or `ViewModelStoreBase`.\ You can modify the default behavior of wrapping in [`makeObservable()` MobX functions](https://mobx.js.org/observable-state.html#makeobservable). Properties of the nested observable configs: ### - `disableWrapping` This removes `makeObservable(this, annotations)`/`makeObservable(this)` calls ### - `useDecorators` This changes the style of marking `MobX` annotations from "decorators style" to "non-decorators style".\ Very helpful if you want to write code with "non decorators style". ::: tip This property has default value - `true` ::: Example: ```ts import { observable, action } from "mobx"; import { viewModelsConfig, ViewModelBase, ViewModelParams } from "mobx-view-model"; viewModelsConfig.observable.viewModels.useDecorators = false; class YourViewModel extends ViewModelBase { constructor(params: ViewModelParams) { super(params); makeObservable(this, { fruitName: observable, setFruitName: action.bound, }) } fruitName: string = ''; setFruitName(fruitName: string) { this.fruitName = fruitName; } } ``` Another example with "decorators style": ```ts import { observable, action } from "mobx"; import { viewModelsConfig, ViewModelBase, ViewModelParams } from "mobx-view-model"; viewModelsConfig.observable.viewModels.useDecorators = true; class YourViewModel extends ViewModelBase { @observable fruitName: string = ''; @action.bound setFruitName(fruitName: string) { this.fruitName = fruitName; } } ``` ### - `custom(context, annotationsArray)` Custom function for wrapping your entity ## global configuration object You can override default global config using import `viewModelsConfig`. ```ts import { viewModelsConfig } from "mobx-view-model"; ``` You should do this before the app starts. ## Usage ```ts import { viewModelsConfig } from "mobx-view-model"; // Configure payload update/reactivity behavior viewModelsConfig.payloadObservable = 'ref'; viewModelsConfig.comparePayload = false; viewModelsConfig.payloadComputed = 'struct'; // Disable view transitions viewModelsConfig.startViewTransitions = { mount: false, payloadChange: false, unmount: false, }; // Optional configurations (uncomment to use) // viewModelsConfig.generateId = () => crypto.randomUUID(); // viewModelsConfig.factory = (config) => new config.VM(rootStore, config); // viewModelsConfig.fallbackComponent = () => ; // viewModelsConfig.onMount = (vm) => console.log('Mounted:', vm.id); // viewModelsConfig.onUnmount = (vm) => console.log('Unmounted:', vm.id); ``` ## Possible causes of infinite re-renders due to payload access The flexible configuration of the payload reactivity and update behavior can lead to infinite re-renders inside the View component.\ This happens when the payload is changing every time the component is re-rendered. The following ViewModel configurations can cause this problem: {circularVmPayloadDependencyTestCases} --- --- url: /mobx-view-model/api/view-models/view-model-simple.md --- # `ViewModelSimple` `ViewModelSimple` is a minimal contract aligned with the [ViewModel interface](/api/view-models/interface), designed for lightweight state management with **MobX**. It keeps reactive state initialization simple (for example, via `makeAutoObservable`) while still fitting the library lifecycle in React applications. [Reference to source code](/src/view-model/view-model-simple.ts) ## When to Use Use `ViewModelSimple` when: 1. You need `direct control over MobX observability` (e.g., using `makeAutoObservable`) 2. You prefer a simple, boilerplate-free class structure 3. Your view model does not require advanced features like [`viewModels` access](/api/view-models/base-implementation.html#viewmodels) or complex lifecycle hooks. ### Example ```ts import { ViewModelSimple } from "mobx-view-model"; import { makeAutoObservable } from "mobx"; export class FruitViewModel implements ViewModelSimple { // Unique instance identifier id = crypto.randomUUID(); // Observable state fruit = "apple"; constructor() { // Initialize MobX observables makeAutoObservable(this); } // Example action setFruit(newFruit: string) { this.fruit = newFruit; } } ``` ::: tip defining `id` property is optional If you do not define the `id` property, a random id will be generated from `viewModelsConfig.generateId` ::: ### Example without implementing any interface methods ```ts{4} import { ViewModelSimple } from "mobx-view-model"; import { makeAutoObservable } from "mobx"; export class FruitViewModel { // Observable state fruit = "apple"; constructor() { // Initialize MobX observables makeAutoObservable(this); } // Example action setFruit(newFruit: string) { this.fruit = newFruit; } } ``` ::: tip `implements ViewModelSimple` was removed Because TypeScript throws an error about not implementing at least one property or method of the `ViewModelSimple` interface. ::: ## Usage in React ### Usage with [`withViewModel`](/react/api/with-view-model) HOC ```tsx import { observer } from "mobx-react-lite"; import { withViewModel } from "mobx-view-model"; import { FruitViewModel } from "./model"; export const FruitComponent = withViewModel(FruitViewModel, ({ model }) => { return (

Current fruit: {model.fruit}

); }); ``` ### Usage with [`useCreateViewModel`](/react/api/use-create-view-model) hook ```tsx import { observer } from "mobx-react-lite"; import { useCreateViewModel } from "mobx-view-model"; import { FruitViewModel } from "./model"; export const FruitComponent = observer(() => { // Creates a single instance per component mount const vm = useCreateViewModel(FruitViewModel); return (

Current fruit: {vm.fruit}

); }); ``` ### Accessing Instances To retrieve an existing instance elsewhere in your app: 1. Use the [`useViewModel`](/react/api/use-view-model) hook. 2. Ensure the instance is registered in a [`ViewModelStore`](/api/view-model-store/overview) --- --- url: /mobx-view-model/react/api/view-models-provider.md --- # `ViewModelsProvider` Component A context provider component that establishes a [ViewModelStore](/api/view-model-store/overview) instance for the React component tree, enabling centralized [ViewModel](/api/view-models/overview) management and cross-component access. ## API Signature ```tsx function ViewModelsProvider(props: { children: ReactNode; value: ViewModelStore }): ReactNode; ``` ### Usage ```tsx import { ViewModelStoreBase, ViewModelsProvider } from "mobx-view-model"; const vmStore = new ViewModelStoreBase(); export const App = () => { return ( ... ) } ``` --- --- url: /mobx-view-model/react/api/with-view-model.md --- # `withViewModel` HOC A Higher-Order Component that connects React components to their [ViewModels](/api/view-models/overview), providing seamless MobX integration. ::: info This HOC wraps your view component into `observer()` HOC! This works because the [`wrapViewsInObserver` option](/api/view-models/view-models-config#wrapviewsinobserver) is enabled by default. ::: ## API Signature ```tsx function withViewModel( ViewModelClass: Class, config?: ViewModelHocConfig ): (Component: ComponentType>) => VMComponent function withViewModel< TViewModel extends AnyViewModel, TCompProps extends AnyObject = ViewModelProps, >( model: Class, component: ComponentType>, config?: ViewModelHocConfig, ): VMComponent; ``` ## Configuration ### `getPayload` This parameter sets the `payload` for `ViewModel` attached to view. Default: `(props) => props.payload` Example:\ *Using all props as "payload" for `ViewModel`* ```tsx class VM extends ViewModelBase { @computed get foo() { return this.payload.foo; } } export const YourComponent = withViewModel(VM, () =>{ return
1
}, { getPayload: (props) => props }); ``` ### `forwardRef` This parameter wraps the React component with the `React.forwardRef` HOC.\ It might be helpful if you need to forward a ref to your `View` component. Using this parameter requires `ViewModelProps` (second generic type `RefType`) to add the `forwardedRef` prop type. Default: `false` ::: info Better to use custom prop This parameter uses `React.forwardRef`, so this is not a good solution for performance.\ Instead of this parameter you can use a custom prop like `targetInputRef`. ::: Examples: ```tsx{3,8} class YourVM extends ViewModelBase {} const Component = withViewModel(YourVM, ({ forwardedRef }) => { // forwardedRef: React.ForwardedRef! return (
hello
) }, { forwardRef: true }) ``` *Case with an explicit `forwardedRef` type* ```tsx{5} class YourVM extends ViewModelBase {} const Component = withViewModel( YourVM, ({ forwardedRef }: ViewModelProps) => { // forwardedRef: React.ForwardedRef! return (
hello
) }, { forwardRef: true } ) ``` ### `factory` This is a factory function for creating ViewModel instances.\ [Same as factory function in `viewModelsConfig`](/api/view-models/view-models-config.html#factory) ### `id` Unique identifier for the view. ### `generateId` Function to generate an identifier for the view model.\ [Same as `generateId` function in `viewModelsConfig`](/api/view-models/view-models-config.html#generateid) ### `reactHook` Function to invoke additional React hooks in the resulting component. :::info This React hook calls before everything what happens inside `withViewModel` HOC. This can be helpful for preprocessing input data. ::: Example: ```tsx import { WithViewModelReactHook } from 'mobx-view-model'; const useSuperReactHook: WithViewModelReactHook = (props) => { props.foo = 1; } class YourVM extends ViewModelBase {} const Component = withViewModel(YourVM, () => { return
1
}, { reactHook: useSuperReactHook, }) ``` ### `fallback` Component to render if the view model initialization takes too long. Example: ```tsx{5,12,13,14} class YourVM extends ViewModelBase { async mount() { await sleep(1000); await fetchData(); super.mount(); } } const Component = withViewModel(YourVM, () => { return
1
}, { fallback: () => { return
loading...
} }) ``` ### `vmConfig` Additional configuration for the `ViewModel`.\ [See `viewModelsConfig` for details](/api/view-models/view-models-config) ### `ctx` Object that contains static, unique data for this HOC call. ### `anchors` Additional React component anchors for the same VM instance.\ When you pass anchor components here, `useViewModel(AnchorComponent)` will return this VM when the connected component is mounted.\ Useful when multiple components need to access the same ViewModel instance. Anchors are stored in config and passed to the store's [`link()`](/api/view-model-store/interface#link) during `processCreateConfig`. Example: ```tsx const Anchor = () => null; const Component = withViewModel(VM, View, { anchors: [Anchor], }); // useViewModel(Anchor) returns the same VM as View receives ``` ### `connect(anchor)` Registers additional anchors dynamically.\ Each anchor is added to `config.anchors`; `useViewModel(anchor)` will return this VM when the connected component is mounted.\ Use `connect()` when the anchor is defined elsewhere or when using the curried form without config. Example: ```tsx const Anchor = () => null; const Component = withViewModel(VM, { generateId: createIdGenerator() })(View).connect(Anchor); // In another component: const model = useViewModel(Anchor); // returns the same VM as View receives ``` ## Usage ### 1. Basic Usage (Default Configuration) ```tsx export const YourComponent = withViewModel(VMClass)(ViewComponent); export const YourComponent = withViewModel(VMClass, ViewComponent); ``` ### 2. Custom Configuration ```tsx export const YourComponent = withViewModel(VMClass, { vmConfig: {}, // vmConfig ctx: {}, // internal object used as cache key source inside this HOC factory: (config) => new config.VM(config), // factory method for creating VM instances fallback: () =>
loading
, // fallback while your VM is mounting/loading generateId, // custom fn for generating ids for VM instances getPayload: (props) => props.payload, // function to get payload data from props id, // unique id if you need to create 1 instance of your VM anchors: [], // additional components for useViewModel lookup reactHook: (allProps, ctx, viewModels) => void 0, // hook for integration inside render HOC component })(ViewComponent) export const YourComponent = withViewModel(VMClass, ViewComponent, { vmConfig: {}, // vmConfig ctx: {}, // internal object used as cache key source inside this HOC factory: (config) => new config.VM(config), // factory method for creating VM instances fallback: () =>
loading
, // fallback while your VM is mounting/loading generateId, // custom fn for generating ids for VM instances getPayload: (props) => props.payload, // function to get payload data from props id, // unique id if you need to create 1 instance of your VM anchors: [], // additional components for useViewModel lookup reactHook: (allProps, ctx, viewModels) => void 0, // hook for integration inside render HOC component }) ``` #### Examples: ```tsx import { ViewModelBase, ViewModelProps, withViewModel } from "mobx-view-model"; import { observer } from "mobx-react-lite"; import { observable, action } from "mobx"; class VM extends ViewModelBase { @observable accessor value = ''; @action setValue = (value: string) => { this.value = value; } } const ComponentView = observer(({ model }: ViewModelProps) => { return (
model.setValue(e.target.value)} />
) }) export const YourComponent = withViewModel(VM)(ComponentView); export const AnotherComponent = withViewModel(VM, ({ model }) => { return (
model.setValue(e.target.value)} />
) }) ``` ## Incompatibility with `` and `lazy()` The `withViewModel` HOC is not compatible with the React's built-in [``](https://react.dev/reference/react/Suspense) component and [`lazy()`](https://react.dev/reference/react/lazy) function. Using `Suspense` and `lazy` with `withViewModel` HOC can lead to unexpected behavior and bugs due to double/triple calls of `useMemo` or lazy `useState` hooks inside [`useCreateViewModel`](/react/api/use-create-view-model) hook. To avoid this issue, either avoid using `Suspense`/`lazy` with this HOC or use `loadable()` from `react-simple-loadable` in your app code. ## Generic types for your wrapped `ViewModel` in this HOC When using this HOC you can run into a limitation: you cannot pass generic types for your `ViewModel`. For example: ```tsx{3,9} type JediType = 'defender' | 'guard' | 'consul' export class JediVM extends ViewModelBase<{ jedi: TJediType }> { get jediType() { return this.payload.jedi; } } const Jedi = withViewModel>(JediVM, ({ model }) => { return (
{model.jediType}
) }) // Anyway `TJediType` will be `JediType`, but should be 'defender' ``` To enable generic types you need to cast the output `Jedi` component to a specific type: ```tsx{1,7-9} const Jedi = withViewModel(JediVM, ({ model }) => { return (
{model.jediType}
) }) as unknown as ( props: VMComponentProps>, ) => React.ReactNode ``` This can be helpful if you need to customize the `payload` of your `ViewModel` based on generic types. --- --- url: /mobx-view-model/other/dependent-packages.md --- # Dependent Packages List of dependent `NPM` packages and libraries that are based on this package or use it. ## mobx-wouter `MobX` integration with `Wouter` [**GitHub**](https://github.com/js2me/mobx-wouter) ## mobx-route `MobX` client-side routing [**GitHub**](https://github.com/js2me/mobx-route) ## mobx-react-routing `MobX` integration with `react-router-dom` [**GitHub**](https://github.com/js2me/mobx-react-routing) --- --- url: /mobx-view-model/introduction/usage/detailed-configuration.md --- # Detailed configuration This approach can be helpful when: * you need to override the default factory method for creating view model instances in [ViewModelStore](/api/view-model-store/interface); * you need to inject a root store into [ViewModelStore](/api/view-model-store/interface); * you need more control over mounting/unmounting [ViewModels](/api/view-models/overview). Follow the steps: ##### 1. Make your own `ViewModel` interface and implementation with customizations: ```ts{9,10} // view-model.ts // interface for your view model import { ViewModel as ViewModelBase } from 'mobx-view-model'; export interface ViewModel< Payload extends AnyObject = EmptyObject, ParentViewModel extends ViewModel | null = null, > extends ViewModelBase { trackName: string; getTrackTime(): Date; } ``` ```ts{5,12,14,16,17,18} // view-model.impl.ts // implementation for your interface import { ViewModelBase, ViewModelParams } from 'mobx-view-model'; import { ViewModel } from './view-model'; export class ViewModelImpl< Payload extends AnyObject = EmptyObject, ParentViewModel extends ViewModel | null = null, > extends ViewModelBase implements ViewModel { trackName = new Date().toISOString() getTrackTime() { return new Date(); } } ``` ##### 2. Make your own `ViewModelStore` implementation ```ts{8,19,20,21} // view-model.store.impl.ts import { ViewModelParams, ViewModelStoreBase, ViewModel, ViewModelCreateConfig, } from 'mobx-view-model'; import { ViewModelImpl } from "./view-model.impl.ts" export class ViewModelStoreImpl extends ViewModelStoreBase { createViewModel>>( config: ViewModelCreateConfig, ): VM { const VM = config.VM; // here you send rootStore as // first argument into VM (your view model implementation) if (ViewModelImpl.isPrototypeOf(VM)) { const instance = super.createViewModel(config) as unknown as ViewModelImpl; console.log(instance.getTrackTime()); return instance; } // otherwise it will be the default behavior // of this method return super.createViewModel(config); } } ``` ##### 3. Create a `View` with a `ViewModel` ```tsx{2,4,10} import { ViewModelProps, withViewModel } from 'mobx-view-model'; import { ViewModelImpl } from '@/shared/lib/mobx'; export class MyPageVM extends ViewModelImpl { @observable accessor state = ''; async mount() { // this.isMounted = false; console.log(this.trackName) super.mount(); // this.isMounted = true } protected didMount() { console.info('did mount'); } unmount() { super.unmount(); } } export const MyPage = withViewModel(MyPageVM, ({ model }) => { return
{model.state}
; }); ``` You may also find [**this recipe about integrating with `RootStore`**](/recipes/integration-with-root-store) helpful. --- --- url: /mobx-view-model/errors/1.md --- # Error `#1`: Active `ViewModel` not found This happened because `vmLookup` for the [`useViewModel`](/react/api/use-view-model) hook is not provided, and the hook tries to look up the active view model using `ActiveViewModelContext`, which exists only when using the [`withViewModel`](/react/api/with-view-model) HOC. ## Explanation: This usage of hook [`useViewModel`](/react/api/use-view-model) ```tsx const model = useViewModel(); ``` Will use `ActiveViewModelContext` which exist only with usage [`withViewModel`](/react/api/with-view-model) HOC. This can also happen if you are trying to access an active `ViewModel` that does not exist in the current React render tree: ```tsx useViewModel() - this provide active `ViewModel` (withViewModel) ``` ## Potential solution The potential solution to this problem is to pass the [`vmLookup`](/api/other/view-model-lookup) to the [`useViewModel`](/react/api/use-view-model) hook: ```tsx const model = useViewModel(YourVM); const model = useViewModel("idofyourvm"); ``` Or use `useViewModel()` with the correct active `ViewModel` in the React render tree: ```tsx - this provide active `ViewModel` (withViewModel) useViewModel() ``` --- --- url: /mobx-view-model/errors/2.md --- # Error `#2`: `ViewModel` not found This happened because the [`vmLookup`](/api/other/view-model-lookup) provided for the [`useViewModel`](/react/api/use-view-model) hook was not found in [`ViewModelStore`](/api/view-model-store/overview). ## Explanation: Hook ```tsx const model = useViewModel(YourVM); ``` Will use [`ViewModelStore`](/api/view-model-store/overview) to find your instance of provided `YourVM` [`ViewModel`](/api/view-models/overview).\ It means that your `ViewModel` is not created yet and not registered in `ViewModelStore`, or you have not declared the creation of this `ViewModel` anywhere. ## Potential solution Create and register your `ViewModel` using [`useCreateViewModel`](/react/api/use-create-view-model) hook or [`withViewModel`](/react/api/with-view-model) HOC. ```tsx const model = useCreateViewModel(YourVM); ``` You can also create and register your `ViewModel` using the [ViewModelStore.attach() method](/api/view-model-store/interface#attach-viewmodel), but then you will need to reproduce the full creation and destruction cycle, which is implemented inside the hook. --- --- url: /mobx-view-model/errors/3.md --- # Error `#3`: No access to `ViewModelStore` This happened because the `viewModels` param is not provided when creating a [`ViewModelBase`](/api/view-models/base-implementation) instance. ## Explanation: This can happen if you override or implement the `createViewModel()` method in your custom [`ViewModelStore`](/api/view-model-store/overview) or [`ViewModelStoreBase`](/api/view-model-store/base-implementation) and forget to pass the `viewModels` param to the ViewModel creation params. ```ts{18} import { ViewModelStoreBase, ViewModel, ViewModelCreateConfig } from 'mobx-view-model'; export class ViewModelStoreImpl extends ViewModelStoreBase { constructor(protected rootStore: RootStore) { super(); } createViewModel>>( config: ViewModelCreateConfig, ): VM { const VM = config.VM; const yourCustomConfig = { ...config, viewModels: undefined, } return new VM(yourCustomConfig); } } ``` ::: info `config` argument in `createViewModel` method already should has `viewModels` property Because it gets passed from [``](/react/api/view-models-provider)\ Otherwise it will be `undefined`\ ::: ## Potential solution #### 1. Add `ViewModelsProvider` Use [``](/react/api/view-models-provider) to pass `viewModels` for all created VM instances in React. Example: ```tsx export const App = () => { return ...; }; ``` #### 2. Check your custom `createViewModel()` implementation Check that you pass the `viewModels` param to the ViewModel creation params. --- --- url: /mobx-view-model/introduction/getting-started.md --- # Getting started The `mobx-view-model` source code is written in TypeScript and compiled with the `NodeNext` module target. ## Requirements * [`MobX`](https://mobx.js.org) **^6** * [`React`](https://reactjs.org) **^18|^19** is required for the React integration ## Installation ::: code-group ```bash [npm] npm install {packageJson.name} ``` ```bash [pnpm] pnpm add {packageJson.name} ``` ```bash [yarn] yarn add {packageJson.name} ``` ::: ## Writing your first ViewModel ```ts import { action, observable } from 'mobx'; import { ViewModelBase } from 'mobx-view-model'; class PetCardVM extends ViewModelBase { @observable accessor petName: string = ''; @action.bound setPetName(petName: string) { this.petName = petName; } } ``` ## Integration with React ```tsx import { observer } from "mobx-react-lite"; import { withViewModel, ViewModelProps } from "mobx-view-model"; import { PetCardVM } from "./model"; export const PetCard = withViewModel(PetCardVM, ({ model }) => { return (
{`Pet name: ${model.petName}`} { model.setPetName(e.target.value); }} />
) }) ... ``` --- --- url: /mobx-view-model/recipes/integration-with-root-store.md --- # Integration with `RootStore` This recipe may be helpful if you need access to your `RootStore` inside your `ViewModel` implementations. Follow the steps: 1. Make your own `ViewModel` implementation that accepts `RootStore` as a `constructor` parameter ```ts // view-model.ts // interface for your view model import { ViewModel as ViewModelBase } from 'mobx-view-model'; export interface ViewModel< Payload extends AnyObject = EmptyObject, ParentViewModel extends ViewModel | null = null, > extends ViewModelBase {} ``` ```ts{6,16} // view-model.impl.ts // implementation for your interface import { ViewModelBase, ViewModelParams } from 'mobx-view-model'; import { ViewModel } from './view-model'; import { RootStore } from "@/shared/store"; export class ViewModelImpl< Payload extends AnyObject = EmptyObject, ParentViewModel extends ViewModel | null = null, > extends ViewModelBase implements ViewModel { constructor( protected rootStore: RootStore, params: ViewModelParams, ) { super(params); } // example of your custom methods // and properties get queryParams() { return this.rootStore.router.queryParams.data; } } ``` 2. Make your own `ViewModelStore` implementation that accepts `RootStore` as a `constructor` parameter and overrides `createViewModel` to pass `rootStore` ```ts{8,9,12,23,24,25} // view-model.store.impl.ts import { ViewModelParams, ViewModelStoreBase, ViewModel, ViewModelCreateConfig, } from 'mobx-view-model'; import { ViewModelImpl } from "./view-model.impl.ts" import { RootStore } from "@/shared/store"; export class ViewModelStoreImpl extends ViewModelStoreBase { constructor(protected rootStore: RootStore) { super(); } createViewModel>>( config: ViewModelCreateConfig, ): VM { const VM = config.VM; // here is you sending rootStore as // first argument into VM (your view model implementation) if (ViewModelImpl.isPrototypeOf(VM)) { return new VM(this.rootStore, config); } // otherwise it will be the default behavior // of this method return super.createViewModel(config); } } ``` 3. Add `ViewModelStore` into your `RootStore` ```ts{8} import { ViewModelStore } from 'mobx-view-model'; import { ViewModelStoreImpl } from '@/shared/lib/mobx'; export class RootStoreImpl implements RootStore { viewModels: ViewModelStore; constructor() { this.viewModels = new ViewModelStoreImpl(this); } } ``` 4. Create a `View` with a `ViewModel` ```tsx{2,4,10} import { ViewModelProps, withViewModel } from 'mobx-view-model'; import { ViewModelImpl } from '@/shared/lib/mobx'; export class MyPageVM extends ViewModelImpl { @observable accessor state = ''; async mount() { // this.isMounted = false; await this.rootStore.beerApi.takeBeer(); super.mount(); // this.isMounted = true } protected didMount() { console.info('did mount'); } unmount() { super.unmount(); } } const MyPageView = observer(({ model }: ViewModelProps) => { return
{model.state}
; }); export const MyPage = withViewModel(MyPageVM, MyPageView); ``` --- --- url: /mobx-view-model/introduction/decorators.md --- # MobX decorators and other If you want to use decorators in your view models you need to configure your build.\ Most of the documentation uses accessor decorators that work only with Babel. You can replace them with [`makeObservable`](https://mobx.js.org/observable-state.html#makeobservable) or [`extendObservable`](https://mobx.js.org/api.html#extendobservable) from MobX. Base implementations of [`ViewModelStore`](/api/view-model-store/interface) and [`ViewModel`](/api/view-models/interface) are using `makeObservable(this)` in class constructor. ## No-decorators approach You need to disable the "decorators style" for wrapping base entities with MobX functions like `makeObservable`.\ To achieve this, configure the [global `viewModelsConfig`](/api/view-models/view-models-config): ```ts import { viewModelsConfig } from "mobx-view-model"; viewModelsConfig.observable.viewModels.useDecorators = false; ``` Example of usage: ```ts import { observable, action } from "mobx"; import { ViewModelBase, ViewModelParams } from "mobx-view-model"; class YourViewModel extends ViewModelBase { constructor(params: ViewModelParams) { super(params); makeObservable(this, { fruitName: observable, setFruitName: action.bound, }) } fruitName: string = ''; setFruitName(fruitName: string) { this.fruitName = fruitName; } } ``` --- --- url: /mobx-view-model/introduction/overview.md --- # Overview **mobx-view-model** is a library for integrating the [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) pattern with MobX and React. ## Motivation The main goal of this library is to integrate the [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) architectural pattern with `React` and `MobX` reactivity. A key aspect of this integration is enabling strict isolation between business logic and presentation layers. By enforcing a clear separation through [ViewModels](/api/view-models/overview), developers can: 1. Encapsulate state management and business rules within observable [ViewModels](/api/view-models/overview) 2. Keep React components focused only on rendering and user interactions 3. Eliminate direct dependencies between UI components and domain models ## Pros and cons Pros: * More convenient separation of business logic from the presentation layer ([React](https://react.dev/)/etc). * More flexible and seamless integration of the `React` ecosystem with `MobX`. Cons: * An additional wrapper in the form of the [`withViewModel() HOC`](/react/api/with-view-model), which wraps the component in an extra layer. This wrapper further encloses the view component within [`observer()`](https://mobx.js.org/api.html#observer). * Additional kilobytes for your bundle. ## About MVVM [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) is an architectural pattern in computer software that facilitates the separation of the development of a graphical user interface (GUI; the view)—be it via a markup language or GUI code—from the development of the business logic or back-end logic (the model). This ensures the view is not dependent on any specific model platform. ![](/assets/mvvm.CmbRw1Kl.png) --- --- url: /mobx-view-model/introduction/playground.md --- # Playground You can try it out in [CodeSandbox](https://codesandbox.io/p/sandbox/nl8kzc). --- --- url: /mobx-view-model/other/project-examples.md --- # Project Examples ## **GitlabActivityAnalyzer** *Links*: * Source: https://github.com/js2me/gitlab-activity-analyzer ## **HTTP Status Codes** *Links*: * Source: https://github.com/js2me/http-status-codes * GitHub Pages: https://js2me.github.io/http-status-codes/#/ ## **Time Tracker app** *Links*: * Source: https://github.com/js2me/time-tracker-app * GitHub Pages: https://js2me.github.io/time-tracker-app/ --- --- url: /mobx-view-model/react/integration.md --- # Integration with React Integration consists of **2-3 steps**. ## 1. Connect ViewModel with View Your [ViewModel](/api/view-models/interface) should be connected to a React view component.\ To achieve this you can use: * [`withViewModel()` HOC](/react/api/with-view-model) - recommended way * [`useCreateViewModel()` hook](/react/api/use-create-view-model) - simplest way, more performant in many cases ## 2. Render in React tree #### use [withViewModel() HOC](/react/api/with-view-model) Then you should render the component returned from this function ```tsx import { ViewModelBase, ViewModelProps } from "mobx-view-model"; import { observer } from "mobx-react-lite"; class YourComponentVM extends ViewModelBase {} export interface YourComponentProps extends ViewModelProps { yourProp?: string; } const YourComponent = withViewModel( YourComponentVM, ({ model, yourProp }: YourComponentProps) => { return
{model.id}
; }, ); const YourApp = () => { return ( ) } ``` #### use [`useCreateViewModel()` hook](/react/api/use-create-view-model) Then you should render your React components using this hook ```tsx import { ViewModelBase, useCreateViewModel } from "mobx-view-model"; import { observer } from "mobx-react-lite"; class YourComponentVM extends ViewModelBase {} const YourComponent = observer(() => { const model = useCreateViewModel(YourComponentVM); return (
{model.id}
) }) const YourApp = () => { return ( ) } ``` ## 3. *\[Optional]* Use [ViewModelStore](/api/view-model-store/interface) [ViewModelStore](/api/view-model-store/interface) is a powerful tool that allows you to look up and access your view model instances anywhere.\ To use this store: 1. Create instance of `ViewModelStore` 2. Wrap your application into [`ViewModelsProvider`](/react/api/view-models-provider) Context Provider. ```tsx import { ViewModelsProvider, ViewModelStoreBase } from "mobx-view-model"; const vmStore = new ViewModelStoreBase(); const YourApp = () => { return ( ... ) } ``` With this step you can use the [`useViewModel()`](/react/api/use-view-model) hook with the first argument ::: tip [`isMounted`](/api/view-models/interface#ismounted-boolean) state\ This state is based on calling the [`mount()` method](/api/view-models/interface#mount-void-promise-void), which is triggered inside the [`useCreateViewModel()`](/react/api/use-create-view-model) hook or the store lifecycle.\ Because of this, on the first render `isMounted` will be `false`, since mounting happens inside a `useLayoutEffect`/`useEffect` hook.\ ::: ::: warning Do not calls [`mount()`](/api/view-models/interface#mount-void-promise-void), [`unmount()`](/api/view-models/interface#unmount-void-promise-void) manually\ This methods already calling inside [`ViewModelStore` base implementation](/api/view-model-store/base-implementation) or inside [`useCreateViewModel`](/react/api/use-create-view-model) hook. ::: --- --- url: /mobx-view-model/react/ssr.md --- # Server-Side Rendering SSR is supported with **ViewModelStore** and predictable hydration on the client.\ The main requirement is to keep the initial render **identical** on server and client. #### Next.js `mobx-view-model` works in Next.js **only with `"use client"`**.\ `"use server"` does not work because Next.js forbids React hooks in server components. ## Basic SSR with `withViewModel()` Use a shared `ViewModelStore` and pass the same payload on server and client.\ This guarantees identical HTML for hydration. ```tsx import { ViewModelBase, ViewModelsProvider, ViewModelStoreBase, withViewModel } from "mobx-view-model"; class PageVM extends ViewModelBase<{ count: number }> {} const Page = withViewModel( PageVM, ({ model }) =>
{`count ${model.payload.count}`}
, ); export const renderPage = (count: number) => { const vmStore = new ViewModelStoreBase(); return ( ); }; ``` ## Hydration with preloaded data If you preload data on the server, pass the same payload to the client. ```tsx import { ViewModelStoreBase, ViewModelsProvider } from "mobx-view-model"; import { hydrateRoot } from "react-dom/client"; const vmStore = new ViewModelStoreBase(); hydrateRoot( document.getElementById("app")!, , ); ``` ## Async `mount()` If your `mount()` is async, the initial render will show **fallback** both on server and client.\ This prevents hydration mismatches. ```tsx class PageVM extends ViewModelBase { async mount() { await sleep(100); super.mount(); } } ``` :::: tip Keep SSR and CSR identical\ Use the same `payload` and `ViewModelStore` setup on server and client.\ Do not rely on side effects inside `mount()` for the first render.\ :::: --- --- url: /mobx-view-model/introduction/usage/simple.md --- # Simple usage The simplest way to integrate with this library is to use the [`ViewModelSimple` interface](/api/view-models/view-model-simple). Follow the steps: ##### 1. Create class or implement [`ViewModelSimple` interface](/api/view-models/view-model-simple) ```tsx import { ViewModelSimple } from 'mobx-view-model'; import { makeAutoObservable } from 'mobx'; // export class MyPageVM implements ViewModelSimple { export class MyPageVM { state = ''; constructor() { makeAutoObservable(this); } setState = (state: string) => { this.state = state } } ``` ##### 2. Create an instance of your `ViewModel` using [`withViewModel()` HOC](/react/api/with-view-model) ```tsx import { withViewModel } from 'mobx-view-model'; const MyPage = withViewModel(MyPageVM, ({ model }) => { return
{model.state}
; }); ``` ##### 3. Use it ```tsx ``` If you need access to more lifecycle methods or the full [ViewModel interface](/api/view-models/interface),\ you can find that guide [on the next page](/introduction/usage/with-base-implementation). --- --- url: /mobx-view-model/introduction/usage/with-view-model-store.md --- # Usage with View Model Store ViewModelStore lets you access your view model instances from anywhere and gives you more control over creating them.\ Follow the simplest way to add a view model store to your application: ##### **1.** Create a class implementing the [ViewModelStore interface](/api/view-model-store/interface) or use [basic library implementation (ViewModelStoreBase)](/api/view-model-store/base-implementation). ```tsx title="/src/shared/lib/mobx/view-model-store.ts" import { ViewModelStoreBase } from "mobx-view-model"; class MyViewModelStore extends ViewModelStoreBase {} ``` ##### **2.** Create an instance of the [ViewModelStore](/api/view-model-store/overview) ```ts const viewModelStore = new MyViewModelStore() // or new ViewModelStoreBase ``` ##### **3.** Integrate with [React](https://react.dev/) using [`ViewModelsProvider`](/react/api/view-models-provider) at the root of your application ```tsx ... ``` ##### **4.** Get access to `ViewModelStore` inside your `ViewModels` ```ts import { ViewModelBase } from "mobx-view-model"; import { ParentVM } from "../parent-vm"; import { ChildVM } from "../child-vm"; import { AppLayoutVM } from "@/app-layout" export class YourVM extends ViewModelBase { get parentData() { return this.viewModels.get(ParentVM)?.data; } get childData() { return this.viewModels.get(ChildVM)?.data; } get appLayoutData() { return this.viewModels.get(AppLayoutVM)?.data; } } ``` --- --- url: /mobx-view-model/recipes/generic-view-models-in-react.md --- # Use generic ViewModel types in React components This recipe shows how to keep a generic type (`TUser`) in your `ViewModel` and pass it through a React component built with [`withViewModel`](/react/api/with-view-model). What you will need: * A `ViewModel` implementation with generic types (for example `UserSelectVM`) * [`withViewModel`](/react/api/with-view-model) HOC * `VMComponentProps` types What you will do: * Add a generic parameter to your view component props * Use `ViewModelProps` to connect props to a generic `ViewModel` * Cast the resulting component to preserve the generic call signature ```tsx{3,10,11,20,21,22} // view.tsx interface UserSelectUIProps { loading?: boolean; className?: string; render: (item: TUser) => ReactNode; } export const UserSelect = withViewModel< UserSelectVM, UserSelectUIProps >( UserSelectVM, ({ model, ...uiProps, }) => { ... }, ) as unknown as ( props: VMComponentProps, UserSelectUIProps>, ) => ReactNode; export type UserSelectProps = ComponentProps< typeof UserSelect >; ``` ```ts // model.ts export class UserSelectVM extends ViewModelBase { ... } ``` Why the cast is needed: * `withViewModel` returns a concrete `ComponentType`, so TypeScript loses the generic call signature. * The explicit `as unknown as (...) => ReactNode` restores the generic component API for consumers. --- --- url: /mobx-view-model/recipes/all-props-as-payload.md --- # Using all props as payload for your `ViewModel` This recipe is helpful if you want to use all props as `payload` (`this.payload`) in your `ViewModel`. What you will need: * [`ViewModelBase`](/api/view-models/base-implementation) or custom implementation of [`ViewModel`](/api/view-models/interface) interface * [`withViewModel`](/react/api/with-view-model) HOC What you will do: * Pass props type into first generic type parameter in `ViewModelBase` class * Configure `withViewModel` HOC with `getPayload` function that returns all props as payload * Override type of output React component into fixed type ```tsx{8,20,22} import { withViewModel } from 'mobx-view-model'; import { ViewModelBase } from 'mobx-view-model'; interface ComponentProps { foo: number; } class YourVM extends ViewModelBase { } export const YourComponent = withViewModel( YourVM, ({ model }) => { return (
{model.payload.foo}
) }, { getPayload: (props) => props } ) as unknown as ComponentType ... ``` --- --- url: /mobx-view-model/api/view-models/base-implementation.md --- # `ViewModelBase` class This is the base implementation of the [`ViewModel`](/api/view-models/interface) interface. [Reference to source code](/src/view-model/view-model.base.ts) ## Methods and properties Here is documentation about **base implementation** methods and properties. ::: info If you want to read about [`ViewModel`](/api/view-models/interface) interface methods and properties [go to interface documentation](/api/view-models/interface)\ ::: ### `viewModels` Reference to an instance of [`ViewModelStore`](/api/view-model-store/overview).\ Allows access to other [`ViewModels`](/api/view-models/interface). #### Example ```ts import { ViewModelBase } from "mobx-view-model"; export class SithCardVM extends ViewModelBase { get power() { return this.viewModels.get(JediCardVM)?.power ?? 0; } } export class StarWarsBattlefieldVM extends ViewModelBase { get jedisCount() { this.viewModels.getAll(JediCardVM).length; } get sithsCount() { this.viewModels.getAll(SithCardVM).length; } } ``` ### `unmountSignal` This is an [`AbortSignal`](https://developer.mozilla.org/ru/docs/Web/API/AbortSignal) that is signaled when your [`ViewModel`](/api/view-models/interface) is unmounted. It happens after `unmount()` completes in the base implementation. #### Example ```ts import { ViewModelBase } from "mobx-view-model"; import { autorun } from "mobx" export class TestVM extends ViewModelBase { protected willMount() { autorun( () => { console.log("log", this.id, this.isMounted); }, { signal: this.unmountSignal } ); } } ``` ### `vmConfig` Configuration object for the view model.\ See [ViewModelsConfig](/api/view-models/view-models-config) for detailed configuration options. ### `isMounted: boolean` Indicates whether the `ViewModel` is currently mounted with its associated component. ### `isUnmounting: boolean` Indicates whether the `ViewModel` is in the process of unmounting. ### `willMount(): void` Called when the component begins mounting in the React tree.\ Executes before the `mount()` method. ### `mount(): void | Promise` Called when the component is mounted in the React tree. This method sets [`isMounted`](/api/view-models/interface#ismounted-boolean) to `true`.\ If you override this method, be sure to call [`super.mount()`](/api/view-models/interface#mount-void-promise-void); otherwise your view component connected to this `ViewModel` will never be rendered because the [`withViewModel`](/react/api/with-view-model) HOC checks the [`isMounted`](/api/view-models/interface#ismounted-boolean) flag before rendering the view component. This method can be async. This feature is helpful if you want to load some data or do something before the view component will be rendered #### Example: Async Mounting ```ts import { ViewModelBase } from "mobx-view-model"; class JediProfileVM extends ViewModelBase<{ jediId: string }> { async mount() { await this.loadJediData(); await super.mount(); } private async loadJediData() { const response = await fetch(`/api/jedi/${this.payload.jediId}`); this.jediData = await response.json(); } } ``` ### `didMount(): void` Called after the view model is fully mounted and ready for use.\ Ideal for post-mount initialization and side effects. #### Example: Post-Mount Actions ```ts import { ViewModelBase } from "mobx-view-model"; class ForceAlertVM extends ViewModelBase<{ message: string }> { protected didMount() { this.rootStore.notifications.push({ type: 'success', title: "May the Force be with you!", message: this.payload.message }); } } ``` ### `willUnmount(): void` Called when the component begins unmounting from the React tree.\ Executes before the `unmount()` method. ### `unmount(): void | Promise` Called when the component is unmounted from the React tree. This method sets [`isMounted`](/api/view-models/interface#ismounted-boolean) to `false`.\ If you override this method, be sure to call [`super.unmount()`](/api/view-models/interface#mount-void-promise-void); otherwise your view component connected to this `ViewModel` will never be unmounted. ### `didUnmount(): void` Called after the view model is fully unmounted.\ Ideal for final cleanup operations. ### [`setPayload(payload: Payload): void`](/api/view-models/interface#setpayload-payload-payload-void) Updates the view model's payload data. The base implementation of this method compares the current payload and the new payload before setting it.\ This can be overridden using [view models configuration](/api/view-models/view-models-config) or by overriding the protected [`isPayloadEqual`](#ispayloadequal-current-payload-next-payload-boolean) method. #### `isPayloadEqual?.(current: Payload, next: Payload): boolean` This method is used for comparing the current and next payloads. You can customize payload comparison overriding this method or configure [`viewModelsConfig`](/api/view-models/view-models-config) Example: ```ts class PostcardBox extends ViewModelBase { isPayloadEqual() { return true; } } ``` ## Additional utility types [Reference to source code](/src/view-model/view-model.base.types.ts) ### `InferViewModelParams` Utility type that infers constructor params for a `ViewModelBase` subclass.\ It resolves to `ViewModelParams` based on the class generics. #### Example ```ts import { ViewModelBase, InferViewModelParams, } from "mobx-view-model"; class UserVM extends ViewModelBase< { userId: string }, null, { isAdmin?: boolean } > { constructor(params: InferViewModelParams) { params.vmConfig = { ...params.vmConfig, comparePayload: 'strict', }; super(params); } } ``` --- --- url: /mobx-view-model/api/view-models/interface.md --- # `ViewModel` interface Interface that defines the core functionality for implementing the MVVM pattern in your application.\ All code examples will include the [base implementation](/api/view-models/base-implementation) of this interface. [Reference to source code](/src/view-model/view-model.ts) ## API Signature ```ts interface ViewModel ``` ## Generics ### 1. `Payload` Declares the payload data type for your `ViewModel`. Used for transferring data between view models or views.\ Must be an object type or `EmptyObject` for no payload. ### 2. `ParentViewModel` Declares the parent `ViewModel` type where current `ViewModel` is rendered in the component hierarchy.\ Enables access to parent view model through the `parentViewModel` property.\ Optional, defaults to `null` if not specified. ## Core Properties ### `id: string` Unique identifier for the view model instance.\ Used for tracking and managing view model lifecycle. ### `vmConfig: ViewModelConfig` Configuration object for the view model.\ See [ViewModelsConfig](/api/view-models/view-models-config) for detailed configuration options. ### `payload: Payload` Data object passed from the parent component to the view model. ### `isMounted: boolean` Indicates whether the `ViewModel` is currently mounted with its associated component.\ Controls the rendering of the connected view component: * `true`: Component is rendered * `false`: Component is not rendered ### `isUnmounting: boolean` Indicates whether the `ViewModel` is in the process of unmounting.\ Used for cleanup and transition states during component unmounting. ### `parentViewModel: ParentViewModel | null` Reference to the parent `ViewModel` in the component hierarchy. ::: warning\ This property is only available when using: * [`ViewModelStore`](/api/view-model-store/interface) (integrated with [ViewModelsProvider](/react/api/view-models-provider)) * [`withViewModel` HOC](/react/api/with-view-model) ::: #### Example: Parent-Child ViewModel Communication ```ts import { ViewModelBase } from "mobx-view-model"; class ParentVM extends ViewModelBase { foo = 'bar' } class ChildVM extends ViewModelBase<{}, ParentVM> { get baz() { return this.parentViewModel?.foo; // 'bar' } } ``` ## Lifecycle Methods ### `mount(): void | Promise` Called when the component is mounted in the React tree. ::: tip The behavior depends on your [ViewModelStore](/api/view-model-store/interface) implementation.\ Base implementation sets `isMounted` to `true` after this method. ::: :::tip Can return a Promise for asynchronous mounting operations. ::: #### Example: Async Mounting ```ts import { ViewModelBase } from "mobx-view-model"; class JediProfileVM extends ViewModelBase<{ jediId: string }> { async mount() { await this.loadJediData(); await super.mount(); } private async loadJediData() { const response = await fetch(`/api/jedi/${this.payload.jediId}`); this.jediData = await response.json(); } } ``` ### `unmount(): void | Promise` Called when the component is unmounted from the React tree. ::: tip Behavior depends on your ViewModelStore implementation.\ Base implementation sets `isMounted` to `false` after this method. ::: :::tip Can return a Promise for asynchronous cleanup operations. ::: ## Payload Management ### `setPayload(payload: Payload): void` Updates the view model's payload data. ::: tip In custom implementations, ensure to update `this.payload` in this method. ::: #### Example: Payload Update with Validation ```ts import { ViewModelBase } from "mobx-view-model"; class LightsaberVM extends ViewModelBase<{ jediId: string }> { @observable accessor currentJediId: string | null = null; setPayload(payload: { jediId: string }) { if (this.currentJediId !== payload.jediId) { this.currentJediId = payload.jediId; this.payload = payload; this.payloadChanged(); } } } ``` ### `payloadChanged(payload: Payload, prevPayload: Payload): void` Called when the payload is updated via [`setPayload()`](/api/view-models/interface#setpayload-payload-payload-void).\ Use this method to handle payload changes and trigger necessary updates. #### Example: Handling Payload Changes ```ts import { ViewModelBase } from "mobx-view-model"; import { runInAction } from "mobx"; class DeathStarVM extends ViewModelBase<{ targetId: string }> { @observable accessor currentTargetId: string | null = null; payloadChanged(payload, prevPayload) { if (this.currentTargetId !== payload.targetId) { runInAction(() => { this.currentTargetId = payload.targetId; this.initializeWeapon(); }); } } private async initializeWeapon() { const response = await fetch(`/api/weapons/${this.currentTargetId}`); this.weaponData = await response.json(); } } ``` --- --- url: /mobx-view-model/api/other/view-model-lookup.md --- # View Model Lookup ### `ViewModelLookup`, `vmLookup` This type declares what data is needed to find your [ViewModel](/api/view-models/overview) instance in [`ViewModelStore`](/api/view-model-store/overview). It can be: * [ViewModel id](/api/view-models/interface#id-string) * `ViewModel class reference` * [`React`](https://react.dev) component created with [`withViewModel()`](/react/api/with-view-model) * Anchor component registered via [config `anchors`](/react/api/with-view-model#anchors) or method [`connect()`](/react/api/with-view-model#connectanchor) [Reference to source code type](/src/view-model/view-model.store.types.ts#L42)\ [Reference to source code with internal usage of this value](/src/view-model/view-model.store.base.ts#L220) # Example *This example represents a scenario where you are not using the [React integration API](/react/integration).* ```ts import { ViewModelStoreBase, ViewModelBase } from "mobx-view-model" const vmStore = new ViewModelStoreBase(); class MyVM extends ViewModelBase { constructor() { super({ id: '1', payload: {} }); } } const vm = new MyVM(); ... await vmStore.attach(vm) // this is required thing ... vmStore.get(vm.id) // instance of MyVM ... ``` --- --- url: /mobx-view-model/api/view-model-store/base-implementation.md --- # `ViewModelStoreBase` class This is the base implementation of the [`ViewModelStore`](/api/view-model-store/interface) interface. [Reference to source code](/src/view-model/view-model.store.base.ts) ## Methods and properties Here is documentation about **base implementation** methods and properties.\ If you need to read about [`ViewModelStore`](/api/view-model-store/interface) interface methods and properties, [go here](/api/view-model-store/interface). ### `viewModels` (*protected*) Map structure with created [ViewModel](/api/view-models/overview) instances in application. ### `instanceAttachedCount` (*protected*) [ViewModel](/api/view-models/interface) instances count attached ([method attach()](/api/view-model-store/interface#attachviewmodel)) to current store ### `mountingViews` (*protected*) A `Set` with [ViewModel](/api/view-models/overview) ids which views are waiting for mount ### `unmountingViews` (*protected*) A `Set` with [ViewModel](/api/view-models/overview) ids which views are waiting for unmount ### `viewModelsTempHeap` (*protected*) A `Map` with temp heap vm instances\ Is needed to get access to view model instance before all initializations happens ### `vmConfig` (*protected*) [ViewModelsConfig](/api/view-models/view-models-config) --- --- url: /mobx-view-model/api/view-model-store/interface.md --- # `ViewModelStore` interface Interface representing a store for managing [`ViewModels`](/api/view-models/interface) ::: tip OPTIONAL USE This is not required for targeted usage of this package, but can be helpful for accessing [ViewModels](/api/view-models/overview) from everywhere by [ViewModelLookup](/api/other/view-model-lookup)\ ::: [Reference to source code](/src/view-model/view-model.store.ts) ## Method and properties ### `getIds(vmLookup)` Retrieves ids of [ViewModels](/api/view-models/interface) based on [vmLookup](/api/other/view-model-lookup). #### Example ```ts vmStore.getIds(MyVM) // ["id"] vmStore.getIds(ViewComponentOfMyVM) // ["id"] ``` ### `getId(vmLookup)` Retrieves the `id` of the **last** [ViewModel](/api/view-models/interface) based on [vmLookup](/api/other/view-model-lookup). #### Example ```ts vmStore.getId(MyVM) // "id" vmStore.getId(ViewComponentOfMyVM) // "id" ``` ### `mountedViewsCount` The total number of views that are currently mounted. ### `has(vmLookup)` Checks whether a [ViewModel](/api/view-models/interface) instance exists in the store.\ Requires [vmLookup](/api/other/view-model-lookup). ### `get(vmLookup)` Retrieves the **last** [ViewModel](/api/view-models/interface) instance from the store based on [vmLookup](/api/other/view-model-lookup). :::tip If you need more than one VM, use [getAll(vmLookup)](#getallvmlookup) method\ ::: #### Example ```ts import { ViewModelBase } from "mobx-view-model"; class UserSelectVM extends ViewModelBase { selectedUser = { id: '1', name: 'John Doe' } } vmStore.get(UserSelectVM)?.selectedUser.id; // '1' ``` ### `getAll(vmLookup)` Retrieves all [ViewModel](/api/view-models/overview) instances from the store based on [vmLookup](/api/other/view-model-lookup). ### `markToBeAttached(viewModel)` Called when a [ViewModel](/api/view-models/overview) is about to be attached to the view.\ This is the first point where the created instance is passed to the store. ### `attach(viewModel)` Attaches a [ViewModel](/api/view-models/overview) to the store. ### `detach(viewModelId)` Detaches a [ViewModel](/api/view-models/overview) from the store using its ID. ### `isAbleToRenderView(viewModelId)` Determines if a [ViewModel](/api/view-models/overview) is able to render based on its ID. ### `createViewModel(config)` Creates a new [ViewModel](/api/view-models/overview) instance based on the provided configuration. Example: ```ts import { ViewModelStoreBase, ViewModel, ViewModelCreateConfig, } from 'mobx-view-model'; export class ViewModelStoreImpl extends ViewModelStoreBase { createViewModel>>( config: ViewModelCreateConfig, ): VM { const VM = config.VM; return new VM(config); } } ``` ### `processCreateConfig(config)` Processes the configuration for creating a [ViewModel](/api/view-models/overview).\ This method is called just before creating a new [ViewModel](/api/view-models/overview) instance.\ It's useful for initializing the configuration, like linking anchors to the [ViewModel](/api/view-models/overview) class.\ The config may contain `anchors` — additional React components that can be used as lookup keys for the same VM instance (e.g. `useViewModel(AnchorComponent)` will return this VM when mounted). ### `link()` Links anchors (React components) with [ViewModel](/api/view-models/overview) class. ### `unlink()` Unlinks anchors (React components) with [ViewModel](/api/view-models/overview) class. ### `generateViewModelId(config)` Generates a unique ID for a [ViewModel](/api/view-models/overview) based on the provided configuration. ### `clean()` Cleans up resources associated with the [ViewModel](/api/view-models/overview) store.\ Cleans all inner data structures. --- --- url: /mobx-view-model/api/view-model-store/overview.md --- # ViewModelStore An optional but powerful container for managing ViewModel instances within a React application. Provides centralized control over ViewModel lifecycle and access. ## Key Features * **Instance Registry** - Automatic tracking of all active ViewModels in React tree * **Cross-Component Access** - Retrieve ViewModels by: * Class reference * React Component reference * Custom unique IDs * **Factory Pattern** - Unified creation interface for ViewModels ## When to Use Consider ViewModelStore when your application requires: * Access to ViewModels outside React hierarchy * Debugging/devtools inspection capabilities * Complex dependency injection scenarios ## Basic usage Integrate with React ```tsx import { ViewModelStoreBase, ViewModelsProvider } from "mobx-view-model"; const vmStore = new ViewModelStoreBase(); const App = () => { return ( ) } ``` ```tsx import { ViewModelBase, withViewModel } from "mobx-view-model"; class NotifierVM extends ViewModelBase { foo = 'foo'; } const NotifierView = () => { return
Hello, I am a notifier.
; }; export const Notifier = withViewModel(NotifierVM, { id: 'notifier-id' })( NotifierView, ); ... // somewhere in your app vmStore.get(Notifier)?.foo // 'foo' | undefined vmStore.get(NotifierVM)?.foo // 'foo' | undefined vmStore.get('notifier-id')?.foo // 'foo' | undefined ``` --- --- url: /mobx-view-model/api/view-models/overview.md --- # ViewModel The ViewModel is a core component of the [`MVVM`](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) architectural pattern, serving as a bridge between the view (UI layer) and model (data/business logic layer). ## Key Responsibilities As the central processing unit for views in your application, the ViewModel: * Manages data flow from models to UI components * Encapsulates view-specific business logic * Maintains a clean separation between presentation and domain layers * Handles component lifecycle and state management * Provides computed properties and actions for UI interaction ## Architectural Role This implementation leverages MobX's granular reactivity system to: 1. Automatically synchronize view updates with ViewModel state changes 2. Maintain strict independence between architectural layers 3. Enable efficient state propagation through observable properties 4. Optimize re-renders using MobX's fine-grained tracking The ViewModel can serve as a mediator that: * **Abstracts complex data transformations** - Formats raw model data for UI consumption * **Handles side effects** - Manages async operations and external interactions * **Exposes clean APIs** - Provides well-defined interfaces for UI components * **Manages component state** - Handles loading, error, and success states * **Controls component lifecycle** - Manages mounting, unmounting, and updates ## Benefits The ViewModel pattern delivers: * **Enhanced testability** - Verify logic without rendering * **Improved maintainability** - Independent layer evolution * **Better collaboration** - Parallel work on UI and business logic * **Reusability** - Share ViewModels across multiple components * **Type safety** - Full TypeScript support with generics ## Example ```ts import { ViewModelBase } from "mobx-view-model"; export class CurrentUserBadgeVM extends ViewModelBase<{ userId: string }> { private userData = /* some data source */ get badgeTitle() { return `user badge: ${this.userData.fullName || ''}` } get isLoading() { return this.userData.isLoading; } } ``` ## Articles * [Understanding MVVM Pattern](https://www.ramotion.com/blog/what-is-mvvm/) * [MobX Documentation](https://mobx.js.org/README.html) * [React Best Practices with MobX](https://mobx.js.org/react-integration.html) --- --- url: /mobx-view-model/other/vite-plugin.md --- # Vite plugin This library has a `Vite` plugin that **can** improve DX when using it. ## Installation ::: code-group ```bash [npm] npm install mobx-view-model-vite-plugin ``` ```bash [pnpm] pnpm add mobx-view-model-vite-plugin ``` ```bash [yarn] yarn add mobx-view-model-vite-plugin ``` ::: ## Usage Open your `vite.config.ts` and add this plugin. ```ts{1,4-6} import { mobxViewModel } from "mobx-view-model-vite-plugin"; ... plugins: [ mobxViewModel({ reloadOnChangeViewModel: true }) ] ... ``` ## Features ### `reloadOnChangeViewModel` This option reloads the page after your `ViewModel` changes. --- --- url: /mobx-view-model/warnings/1.md --- # Warning `#1`: [`ViewModelStore`](/api/view-model-store/interface) not found ## Full Text: Unable to get access to view model by id or class name without using [`ViewModelStore`](/api/view-model-store/interface)\ Last active view model will be returned. ## Explanation: This happened because the [`vmLookup`](/api/other/view-model-lookup) passed as the first argument to the [`useViewModel`](/react/api/use-view-model) hook does not work when [`ViewModelStore`](/api/view-model-store/interface) is not provided by [``](/react/api/view-models-provider). ```tsx const model = useViewModel(YourVM); // where YourVM - is vmLookup ``` This code tries to find `YourVM` in `ViewModelStore`, but `ViewModelStore` is not provided. ## Potential solution To fix this warning message, use [``](/react/api/view-models-provider) to provide [`ViewModelStore`](/api/view-model-store/interface): ```tsx export const App = () => { return ...; }; ``` --- --- url: /mobx-view-model/introduction/usage/with-base-implementation.md --- # With base implementation Another simple usage is to work with the [base implementation](/api/view-models/base-implementation) of the [`ViewModel` interface](/api/view-models/interface). Follow the steps: ##### **1.** Create your [`ViewModel`](/api/view-models/overview) class using [`ViewModelBase`](/api/view-models/base-implementation) (base implementation of [`ViewModel` package interface](/api/view-models/interface)) ```tsx import { observable } from 'mobx'; import { ViewModelProps, ViewModelBase, withViewModel } from 'mobx-view-model'; export class MyPageVM extends ViewModelBase<{ payloadA: string }> { @observable accessor state = ''; mount() { super.mount(); } protected didMount() { console.info('did mount'); } unmount() { super.unmount(); } } ``` ##### **2.** Create view component using [HOC `withViewModel()`](/react/api/with-view-model) ```tsx import { withViewModel } from 'mobx-view-model'; export const MyPage = withViewModel(MyPageVM, ({ model }) => { return
{model.state}
; }); ``` or you can use the [`useCreateViewModel()` hook](/react/api/use-create-view-model) ```tsx import { observer } from 'mobx-react-lite'; import { ViewModelPayload, useCreateViewModel } from 'mobx-view-model'; export const MyPage = observer( ({ payload }: { payload: ViewModelPayload }) => { const model = useCreateViewModel(MyPageVM, payload); return
{model.state}
; }, ); ``` ::: tip don't forget to use the [`observer()` hoc](https://mobx.js.org/react-integration.html#react-integration)\ ::: ##### **3.** Use it ```tsx ``` If you need access to other view models, then you need to add a [ViewModelStore](/api/view-model-store/overview).\ You can find that guide [on the next page](/introduction/usage/with-view-model-store). --- --- url: /mobx-view-model/recipes/observer-wrap-all-view-components.md --- # Wrap in `observer()` all view components All your view components wrapped into [`withViewModel()` HOC](/react/api/with-view-model) are automatically wrapped in [`observer()` MobX HOC](https://mobx.js.org/api.html#observer).\ Because [`wrapViewsInObserver`](/api/view-models/view-models-config.html#wrapviewsinobserver) view model config option is enabled by default. --- --- url: /mobx-view-model/recipes/wrap-view-components-in-custom-hoc.md --- # Wrap view components in custom HOC To achieve this you can use the [`processViewComponent`](/api/view-models/view-models-config.html#processviewcomponent) view model config option. Example: ```tsx import { viewModelsConfig } from "mobx-view-model"; const YourHOC = (Component) => { return (props) => { return ( ) } } viewModelsConfig.processViewComponent = (component) => { return YourHOC(component); }; ```