Tiny package (~0.9 KB) which can make a huge difference for your React/Mobx app architecture.
npm install mobx-react-viewmodelTiny package (~0.9 KB) which can make a huge difference for your React/Mobx app architecture.
Simple hooks useViewModel and useViewModelFactory allow you to move all component's state and logic from the render function to a separate stateful view-model class. Which unleashes the full power of Mobx and helps you escape React Hooks Hell.
Written in TypeScript and 100% type-safe 🎯
```
npm install mobx-react-viewmodel --save
or
``
yarn add mobx-react-viewmodel
`tsx
import { observer } from 'mobx-react'
import { useViewModel } from 'mobx-react-viewmodel'
class MyViewModel {
@observable
title: string = '';
constructor() {
makeObservable(this);
}
@action
setTitle(title: string) {
this.title = title;
}
}
const MyComponent = observer(() => {
const viewModel = useViewModel(MyViewModel); // MyViewModel instance will be created only once on first render
return (
Title: {viewModel.title}
Benefits
- View-layer (component's render function) stays clean and contains only rendering code
- Taking full advantage of Mobx using
@observable / @computed / @action / reaction / when primitives for powerful reactive programming
- No need to use useState / useEffects / useCallback / useMemo anymore (goodbye spaghetti code, stale closure bugs and dependency list for every hook)
- At some point, if you need to move some component's state to a global Mobx-store, you don't have to rewrite old hooks-code with Mobx. You can just cut and paste what you need from view-model class to the store classAdvanced Example
`typescript
// MyPageViewModel.tsimport { action, comparer, computed, makeObservable, observable, reaction } from 'mobx';
import { User, UsersStore } from 'our/users/module'
interface MyPageViewModelProps {
userId: string;
}
class MyPageViewModel extends ViewModel {
@observable
user?: User;
constructor(props: MyPageViewModelProps, private usersStore: UsersStore) {
super(props);
makeObservable(this);
}
// is invoked immediately after a component is mounted
init(){
this.disposers.push(
reaction(
() => this.props.userId,
userId => this.getUserFromStore(userId),
{ fireImmediately: true }
)
);
}
// is invoked immediately before a component is unmounted and destroyed
dispose() {
super.dispose();
this.user = undefined;
}
@action
updateField = (field: keyof User, value: string) => {
if (!this.user) return;
this.user[field] = value;
}
@action
save = () => {
if (!this.user) return;
this.usersStore.updateUser(this.user);
}
@computed
isPristine() {
const storeUser = this.usersStore.getUser(this.props.userId);
return this.user && storeUser && comparer.shallow(this.user, storeUser);
}
@action
private getUserFromStore = (userId: string) => {
const user = this.usersStore.getUser(userId);
if (user) {
this.user = { ...user };
} else {
console.error(
Couldn't find user: ${userId});
}
}
}
``tsx
// MyPage.tsximport { observer } from 'mobx-react'
import { useViewModel } from 'mobx-react-viewmodel'
import { useParams } from 'react-router-dom';
import { useStores } from 'our/stores/path';
const MyPage = observer(() => {
// Get the userId param from the URL.
const { userId } = useParams();
// Get our custom store from hook or context
const { userStore } = useStores();
const viewModel = useViewModelFactory(
props => new MyPageViewModel(props, userStore), // Factory-function will be called only once on first render
{ userId } // These
props are reactive. It will be passed and updated in view-model every time it changes without creating new instance of view-model
);
//** or
// const viewModel = useViewModel(
// MyPageViewModel,
// { userId },
// [userStore] // Extra dependencies for the view-model constructor
// ); return (
type="text"
value={viewModel.user.first_name}
onChange={e => viewModel.updateField('first_name', e.target.value)}
placeholder="First Name"
/>
type="text"
value={viewModel.user.last_name}
onChange={e => viewModel.updateField('last_name', e.target.value)}
placeholder="Last Name"
/>
);
});
`You can find more examples here.
API documentation
$3
Basic hook that creates an instance of the
ViewModelClass on the first render and keeps it alive during all further renders.
If props is passed, it will be set to the ViewModelClass instance props property every time it changes.
If args is passed, it will be passed to the ViewModelClass constructor on the first render. You should use only permanent references to the object there, for example, a reference to singleton objects.
If ViewModelClass implements init() or dispose() methods, they will be called on the component's mount and unmount events. $3
Hook for more advanced view-model instantiation. It allows to inject extra dependencies to the view-model constructor inside the custom
factoryFn. Life-cycle and props update logic is the same as with useViewModel$3
Base class with observable
props property which is automatically updated by useViewModel or useViewModelFactory. Also, it has a built-in disposers array for storing all disposer-functions from reaction, when, or any other custom ones that you need to call on unmount`. You can extend your custom view-model classes from this class for cutting down boilerplate code.