
react-mobx-local-model which was able to solve the problem of local models in react
After writing the note about using useReactModel
, I realized that it is quite difficult to avoid double re-rendering to synchronize props
from the component and the local model. The reactivity of mobx
especially does not like it when the data changes during the rendering process, and when using useEffect
we get a double call of the component.
I came to the conclusion that, unfortunately, this problem can only be avoided by moving the code related to props
synchronization to a separate parent component, and reading it already below, using the mobx
mechanisms.
const SyncComponent = (props) => {
const $value = useMemo(() => observable.box(), []);
useEffect(() => {
runInAction(() => {
value.set(props);
});
}, [props]);
return <UserComponent value={$value} />;
};
const UserComponent = observer(({ $value }) => {
const props = $value.get();
return <div>{props.value}</div>;
});
Given that observer
uses React.memo
under the hood, we avoided re-rendering for UserComponent
and left that for the very lightweight SyncComponent
.
Using this simple trick and the approach from the previous article, the library react-mobx-local-model was born.
With its help, you can write similar code, getting rid of unpleasant unnecessary code along the way and getting a fairly optimized solution that is still well tested.
import { ReactModel, observerWithModel } from 'react-mobx-local-model';
import { computed } from 'mobx';
// Each local model should extend the `ReactModel` class to create a local model for the component
// with the type arguments that will be used in the component later. The constructor of this kind
// of model shouldn't use any parameters because it will be created automatically like `new TestReactModel()`.
class TestReactModel extends ReactModel<{ name: string }> {
constructor() {
super({
name: computed, // Use the computed decorator to optimize prop access if needed
});
}
get text() {
// Access the props using the `get` method
return `Hello ${this.get('name')}`;
}
}
// The component type will be inferred automatically, e.g., `FC<{ model: TestReactModel, name: string }>`
const Component = observerWithModel(TestReactModel, ({ model, name }) => {
return (
<div>
{name} {model.text}
</div>
);
});