---
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 (
)
},
{ 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 (
)
})
```
## 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 (
)
})
...
```
---
---
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.

---
---
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 (