ViewModelsConfig configuration object
This configuration contains all options for the behavior of ViewModel instances.
The package provides a global object with this configuration, but you can also change it for each ViewModel and ViewModelStore separately using the vmConfig field.
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
}
})Recommendations
These are the recommended settings for the global configuration viewModelsConfig, which contain the most optimal values.
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; // falsestartViewTransitions
Controls view transitions for view model lifecycle moments.
MDN Reference
Shape
startViewTransitions is an object with these flags:
mount- start transition when the view mountspayloadChange- start transition when the payload changesunmount- 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.structuralfrom MobX)'shallow'- shallow equalityfalse- (default), (recommended) no comparisonfn- custom payload compare fn (e.g. MobX comparer functions)
payloadComputed
Allows you to configure computed statement of the payload
'struct'- (default), (recommended)computed.structfrom MobXtrue-computedfrom MobXfn- custom equality function forcomputedfrom MobXfalse- do not wrappayloadintocomputedMobX utility
payloadObservable
Indicates type of observable for ViewModel payload.
'ref'- (default) (recommended) MobX ref observable'deep'- MobX deep observable'struct'- MobX struct observable'shallow'- MobX shallow observablefalse- no observable wrapping
generateId()
Generates a unique identifier for a ViewModel.
This property has default implementation here
Example
Using crypto.randomUUID() to generate view model ids
import { viewModelsConfig } from "mobx-view-model";
viewModelsConfig.generateId = () => crypto.randomUUID();factory()
Factory function for creating ViewModel instances.
Can be helpful if you want to add some constructor arguments for your own ViewModel implementation
This property has default implementation here
Example
Passing RootStore as first constructor parameter
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
viewModelsConfig.fallbackComponent = () => (
<div className="loading-spinner">
Loading...
</div>
);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
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
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
viewModelsConfig.processViewComponent = (Component) => {
return (props) => {
return (
<ErrorBoundary>
<Component {...props} />
</ErrorBoundary>
)
}
}It works only for withViewModel HOCs
wrapViewsInObserver
Wrap View components in observer() MobX HOC
This property is enabled by default.
You can turn off this behavior by setting wrapViewsInObserver to false.
Example:
import { viewModelsConfig } from "mobx-view-model";
viewModelsConfig.wrapViewsInObserver = false;It works only for withViewModel HOCs
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.
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".
This property has default value - true
Example:
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":
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.
import { viewModelsConfig } from "mobx-view-model";You should do this before the app starts.
Usage
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 = () => <LoadingSpinner />;
// 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:
{
"wrapViewsInObserver": true,
"payloadComputed": true,
"comparePayload": "shallow",
"payloadObservable": "deep"
}{
"wrapViewsInObserver": true,
"payloadComputed": true,
"comparePayload": false,
"payloadObservable": "deep"
}{
"wrapViewsInObserver": true,
"payloadComputed": true,
"comparePayload": false,
"payloadObservable": "ref"
}{
"wrapViewsInObserver": true,
"payloadComputed": false,
"comparePayload": "shallow",
"payloadObservable": "deep"
}{
"wrapViewsInObserver": true,
"payloadComputed": false,
"comparePayload": false,
"payloadObservable": "deep"
}{
"wrapViewsInObserver": true,
"payloadComputed": false,
"comparePayload": false,
"payloadObservable": "ref"
}{
"wrapViewsInObserver": false,
"payloadComputed": true,
"comparePayload": "shallow",
"payloadObservable": "deep"
}{
"wrapViewsInObserver": false,
"payloadComputed": true,
"comparePayload": false,
"payloadObservable": "deep"
}{
"wrapViewsInObserver": false,
"payloadComputed": true,
"comparePayload": false,
"payloadObservable": "ref"
}{
"wrapViewsInObserver": false,
"payloadComputed": false,
"comparePayload": "shallow",
"payloadObservable": "deep"
}{
"wrapViewsInObserver": false,
"payloadComputed": false,
"comparePayload": false,
"payloadObservable": "deep"
}{
"wrapViewsInObserver": false,
"payloadComputed": false,
"comparePayload": false,
"payloadObservable": "ref"
}