()(({ viewModel }) => (
// [!code focus:2]
{/* If you use the line below, SomeChildView will be re-rendered every time SomeView get any new prop */}
{viewModel.viewProps.prop1}
// [!code focus:2]
{/* If you use the line below, SomeChildView will be re-rendered only if prop1 was changed */}
{viewModel.prop1}
))
// [!code focus]
const SomeView = view(SomeViewModel)(() => )
```
### View lifecycle hooks
You add handle some of the view lifecycle state changes, such as mounting, unmounting and updating. There are 3 methods for each hook in the `ViewModel`.
```tsx
import { ViewModel } from 'react-mvvm'
// Hooks can be sync and async. Also, they can be decorated with @action or other decorators
export class SomeViewModel extends ViewModel {
protected onViewMounted() {
console.log(
'View has been mounted. This function is called in the useEffect hook'
)
}
protected onViewMountedSync() {
console.log(
'View has been mounted. This function is called in the useLayoutEffect hook'
)
}
protected onViewUpdated() {
console.log(
'View has been updated. This function is called in the useEffect hook'
)
}
protected onViewUpdatedSync() {
console.log(
'View has been updated. This function is called in the useLayoutEffect hook'
)
}
protected onViewUnmounted() {
console.log(
'View has been unmounted. This function is called in the useEffect hook'
)
}
protected onViewUnmountedSync() {
console.log(
'View has been unmounted. This function is called in the useLayoutEffect hook'
)
}
}
```
### Creating reactions
To observe anything in a view model, you *should* use ViewModel's `reaction`, `autorun` and `addDisposer` methods. These methods are added to automatically dispose reactions, when the view becomes unmounted. You can also not to use these methods, but in these case there can be a probability of a memory leak formation.
```tsx
import { intercept, makeObservable, observable, observe, when } from 'mobx'
// [!code focus]
import { ViewModel } from 'react-mvvm'
// [!code focus]
export class SomeViewModel extends ViewModel {
// [!code focus]
@observable field = 0
constructor() {
super()
makeObservable(this)
// [!code focus:5]
// If you want to create a reaction, please, use this.reaction instead of reaction from the mobx package
this.reaction(
() => this.field,
(value) => this.doSomething(value)
)
// [!code focus:4]
// If you want to create an autorun, please, use this.reaction instead of reaction from the mobx package
this.autorun(() => {
this.doSomething(this.field)
})
// [!code focus:2]
// In case you want to create other type of observation, such as observe, intercept or when, you can use
// this.addDisposer
// [!code focus:4]
// observe
this.addDisposer(
observe(this, 'field', ({ newValue }) => this.doSomething(newValue))
)
// [!code focus:7]
// intercept
this.addDisposer(
intercept(this, 'field', (change) => {
this.doSomething(change.newValue)
return change
})
)
// [!code focus:4]
// when
const w = when(() => this.field === 1)
w.then(() => this.doSomething(this.field))
this.addDisposer(() => w.cancel())
}
doSomething = (field: number) => {}
}
```
## Configuration
### Configuring `vmFactory`
`vmFactory` tells to views how they should create an instance of a view model. You can configure this function to add debug information or a middleware.
```tsx
import { configure } from 'react-mvvm'
configure({
vmFactory: (VM) => {
// By default, vmFactory returns new VM();
const viewModel = new VM()
// But you can do anything here
// Add debug information
console.log('view model created:', viewModel)
// Or process your view model somehow
;(viewModel as any).__some_special_field = 'some special value'
// vmFactory must return an instance of a ViewModel
return viewModel
},
})
```
### Configuring `Wrapper`
The `Wrapper` is used to wrap all the views and childViews. By default, the `Wrapper` is equal to `React.Fragment` so it doesn't really affect on your application. But you can set any component as wrapper to add debug information or a middleware.
```tsx
// [!code focus]
import { configure } from 'react-mvvm'
import { Component, FC, ReactElement } from 'react'
// [!code focus:3]
// The Wrapper can be declared both with functional style or as class
// It must have children as a prop, at it should return a children. Otherwise, your views will not be shown.
const FunctionalWrapper: FC<{ children: ReactElement }> = ({ children }) => {
// You can add a debug info
console.log('view is rendered')
// [!code focus:2]
// You should return children
return children
}
// [!code focus:2]
// You can also use class components
class ClassWrapper extends Component<{ children: ReactElement }> {
render() {
// [!code focus:7]
// You should also return processed children
return (
Wrapper content
{this.props.children}
)
}
}
// [!code focus:3]
configure({
Wrapper: FunctionalWrapper,
})
// [!code focus:3]
configure({
Wrapper: ClassWrapper,
})
```
# Complex Examples
## Complex Examples
And here's some complex examples of whole React applications with React MVVM. You can find them here [Github](https://github.com/beautyfree/react-mvvm/tree/master/examples).
# Examples
import { Cards, Card } from 'fumadocs-ui/components/card'
## Overview
This section contains comprehensive examples and patterns for using React MVVM in your applications. From basic usage to advanced patterns and real-world use cases.
Learn the fundamentals with View, ChildView, and ViewModel examples
Explore complete React applications built with React MVVM
Discover helpful patterns and tricks to enhance your development workflow
# Useful Examples
## Useful Examples
This section contains some tricks that can simplify you development process.
### Automatic `makeObservable`
If you sure that most case your view models will contain observable fields you can make calling `makeObservable` automatic, so you don't need to call it for each ViewModel separately. But be aware, if you use this code, you should create your reactions in the `onViewMounted` hook instead of constructor due to the fact that view model will not be observable in it.
```tsx
import { makeObservable, observable } from 'mobx'
// [!code focus]
import { configure, ViewModel } from 'react-mvvm'
// [!code focus:7]
configure({
vmFactory: (VM) => {
const viewModel = new VM()
makeObservable(viewModel)
return viewModel
},
})
// [!code focus]
class SomeViewModel extends ViewModel {
@observable field1 = 0
// [!code focus:10]
protected onViewMounted() {
// In case you make view models observable in a vmFactory, and you want to create reactions,
// you should do it in the hook
this.reaction(
() => this.field1,
() => {
// do something
}
)
}
}
```
### Enabling DI pattern
I really like the DI pattern. And I highly recommend you to use this pattern if you application is big. This pattern can have a big impact on the ability to scale your application. With the DI you can create common MobX stores for whole application. Such Redux does, but with DI + MobX these stores can be logically separated, can contain methods and can be easily used at any part of your code, including both views and view models.
```tsx
import { computed, makeObservable, observable } from 'mobx'
// [!code focus]
// It's not necessary to use tsyringe. You can use any library actually
import { injectable, container, singleton } from 'tsyringe'
// [!code focus]
import { configure, ViewModel } from 'react-mvvm'
// [!code focus:3]
configure({
vmFactory: (VM) => container.resolve(VM),
})
// [!code focus:3]
// This is an example of common store for the whole application
@singleton()
class SomeOuterClass {
// [!code focus]
@observable field1 = 0
@observable field2 = 'field2'
constructor() {
makeObservable(this)
}
// [!code focus:3]
doSomething = () => {
// do something
}
}
// [!code focus:3]
// It can also be any singleton or transient class, containing observable fields is not necessary
@injectable()
class SomeOuterClass2 {
@observable field1 = 0
@observable field2 = 'field2'
// [!code focus]
constructor(private someOuterClass: SomeOuterClass) {
makeObservable(this)
}
doSomething = () => {
// do something
}
}
// [!code focus:2]
@injectable()
class SomeViewModel extends ViewModel {
// [!code focus:3]
@computed get someGetter() {
return this.someOuterClass.field1
}
// [!code focus:5]
// And now every ViewModel can access the class via constructor
constructor(
private someOuterClass: SomeOuterClass,
private someOuterClass2: SomeOuterClass2
) {
super()
makeObservable(this)
}
// [!code focus:3]
viewModelFunction = () => {
this.someOuterClass.doSomething()
}
}
// [!code focus:2]
// You can also get an instance of singleton class in the any place of your code
const instance = container.resolve(SomeOuterClass)
```
### Using Error Boundary
React applications have a few problems. One of them is error handling. If some of your component throws an error and you don't handle it, all the virtual DOM tree will die. FaceBook recommends to use ErrorBoundary to handle such errors. But it can be inconvenient to use it - you should always think where to use it, and there can be a lot of repeating code of using the same error boundary. But with this package you can add error boundaries to all of your views and childViews, so you don't actually have to think about using it at all.
```tsx
import { Component, ReactElement } from 'react'
// [!code focus]
import { configure } from 'react-mvvm'
// [!code focus]
class ErrorBoundary extends Component<
{ children: ReactElement },
{ hasError: boolean }
> {
static getDerivedStateFromError() {
return { hasError: true }
}
state = {
hasError: false,
}
componentDidCatch(error: Error) {
// I recommend you to log the error to avoid situations where your content is disappeared,
// and you don't know the reason
console.error(error)
}
render() {
return !this.state.hasError && this.props.children
}
}
// [!code focus:5]
configure({
// That's it. And now if one of your components throws an error it will just disappear. At it
// will be the only component that disappeared.
Wrapper: ErrorBoundary,
})
```
## Complex Examples
And here's some complex examples of whole React applications with React MVVM. You can find them here [Github](https://github.com/beautyfree/react-mvvm/tree/master/examples).