withViewModel HOC
A Higher-Order Component that connects React components to their ViewModels, providing seamless MobX integration.
API Signature
function withViewModel<VM extends AnyViewModel>(
ViewModelClass: Class<VM>,
config?: ViewModelHocConfig<VM>
):
(Component: ComponentType<ComponentProps & ViewModelProps<VM>>) =>
VMComponent
function withViewModel<
TViewModel extends AnyViewModel,
TCompProps extends AnyObject = ViewModelProps<TViewModel>,
>(
model: Class<TViewModel>,
component: ComponentType<TCompProps & ViewModelProps<TViewModel>>,
config?: ViewModelHocConfig<TViewModel>,
): VMComponent<TViewModel, TCompProps>;Configuration
getPayload
This parameter sets the payload for ViewModel attached to view.
Default: (props) => props.payload
Example:
Using all props as "payload" for ViewModel
class VM extends ViewModelBase {
@computed.get
get foo() {
return this.payload.foo;
}
}
export const YourComponent = withViewModel(VM, () =>{
return <div>1</div>
}, {
getPayload: (props) => props
});
<YourComponent foo={'1'} />forwardRef
This parameter wraps React component into React.forwardRef HOC.
It might be helpful if need to forward ref to your View component.
Using this parameter will require to use ViewModelProps<YourVM, RefType> (second generic type RefType) to add type property forwardedRef in your props.
Default: false
Better to use custom prop
This parameter uses React.forwardRef, so this thing is not a good solution for performance.
Instead of thia parameter you can use custom prop like targetInputRef
Examples:
class YourVM extends ViewModelBase {}
const Component = withViewModel(YourVM, ({ forwardedRef }) => {
// forwardedRef: React.ForwardedRef<any>!
return (
<div ref={forwardedRef}>hello</div>
)
}, { forwardRef: true })Case with add type to forwardedRef
class YourVM extends ViewModelBase {}
const Component = withViewModel(
YourVM,
({ forwardedRef }: ViewModelProps<YourVM, HTMLDivElement>) => {
// forwardedRef: React.ForwardedRef<HTMLDivElement>!
return (
<div ref={forwardedRef}>hello</div>
)
},
{ forwardRef: true }
)factory
This is factory function for creating ViewModel instances.
Same as factory function in viewModelsConfig
id
Unique identifier for the view.
generateId
Function to generate an identifier for the view model
Same as generateId function in viewModelsConfig
reactHook
Function to invoke additional React hooks in the resulting component.
This React hook calls before everything what happens inside withViewModel HOC.
That might be helpful to do some tricks with input data.
Example:
import { WithViewModelReactHook } from 'mobx-view-model';
const useSuperReactHook: WithViewModelReactHook = (props) => {
props.foo = 1;
}
class YourVM extends ViewModelBase {}
const Component = withViewModel(YourVM, () => {
return <div>1</div>
}, {
reactHook: useSuperReactHook,
})fallback
Component to render if the view model initialization takes too long
Example:
class YourVM extends ViewModelBase {
async mount() {
await sleep(1000);
await fetchData();
super.mount();
}
}
const Component = withViewModel(YourVM, () => {
return <div>1</div>
}, {
fallback: () => {
return <div>loading...</div>
}
})vmConfig
Additional configuration for the ViewModel
See viewModelsConfig for details
ctx
Object which contains some static unique based on this HOC call data.
Usage
1. Basic Usage (Default Configuration)
export const YourComponent = withViewModel(VMClass)(ViewComponent);
export const YourComponent = withViewModel(VMClass, ViewComponent);2. Custom Configuration
export const YourComponent = withViewModel(VMClass, {
vmConfig: {}, // vmConfig
ctx: {}, // internal object as source for all cache inside this HOC
factory: (config) => new config.VM(config), // factory method for creating VM instances
fallback: () => <div>loading</div>, // fallback component for cases when your VM is mounting\loading
generateId, // custom fn for generate id for this VM instances
getPayload: (props) => props.payload, // function to getting payload data from props
id, // unique id if you need to create 1 instance of your VM
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 as source for all cache inside this HOC
factory: (config) => new config.VM(config), // factory method for creating VM instances
fallback: () => <div>loading</div>, // fallback component for cases when your VM is mounting\loading
generateId, // custom fn for generate id for this VM instances
getPayload: (props) => props.payload, // function to getting payload data from props
id, // unique id if you need to create 1 instance of your VM
reactHook: (allProps, ctx, viewModels) => void 0, // hook for integration inside render HOC component
})Examples:
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<VM>) => {
return (
<div>
<input
value={model.value}
onChange={e => model.setValue(e.target.value)}
/>
</div>
)
})
export const YourComponent = withViewModel(VM)(ComponentView);
export const AnotherComponent = withViewModel(VM, ({ model }) => {
return (
<div>
<input
className={"bg-[red]"}
value={model.value}
onChange={e => model.setValue(e.target.value)}
/>
</div>
)
})Incompatibility with <Suspense /> and lazy()
The withViewModel HOC is not compatible with the React's built-in <Suspense /> component and 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 hook.
To avoid this issue, it is recommended to use withLazyViewModel HOC instead.
Generic types for your wrapped ViewModel into this HOC
Using this HOC you encounter some limitation that you unable to pass generic types for your ViewModel. For example:
type JediType = 'defender' | 'guard' | 'consul'
export class JediVM<TJediType extends JediType> extends ViewModelBase<{ jedi: TJediType }> {
get jediType() {
return this.payload.jedi;
}
}
const Jedi = withViewModel<JediVM<JediType>>(JediVM, ({ model }) => {
return (
<div>
{model.jediType}
</div>
)
})
<Jedi payload={{ jedi: 'defender' }} />
// Anyway `TJediType` will be `JediType`, but should be 'defender'To achieve ability to use generic types you need to cast output Jedi component into specific type:
const Jedi = withViewModel(JediVM<JediType>, ({ model }) => {
return (
<div>
{model.jediType}
</div>
)
}) as <TJediType extends JediType>(
props: VMComponentProps<JediVM<TJediType>>,
) => React.ReactNodeThis might be helpful if you need to customize "payload" of your ViewModel based on generic types.
