Using mobx with redux in the same project - even in the same component/class

Viewed 1373

A bit of background first:

In in the process of learning react and redux, using Typescript. For components with some kind of application state (rather than pure UI state), I am trying to keep a logical separation between the react components and the underlying logic and state. To that end, for components that work with application state data in some way I have implemented a ViewModel class that is set on the component props and contains the properties and actions that encapsulate that components behaviour. So the ViewModel would (for example), fetch data from the backend and present it is properties to the react component.

One reason for implementing this separation is to ensure that the ViewModel can be unit tested independently of the purely view related aspects contained within the react component. I feel it is important both for the initial implementation and also in future should the logic need to be re-implemented in some future architecture - so that is remains understandable and self contained without a set of "wiring" to a react, redux or mobx specific architecture.

I have implemented this using mobx (@observable and @action decorators in the ViewModel, bound to a component decorated with @observer). All of the state I am implementing so far in this way is just located within the component, or passed down to child components using the ViewModel hierarchy. It is working really well so far, and is a pattern that is easy to apply to new components and won't be a huge leap for devs familiar with knockout etc. which are all positives for my current situation.

However, I am now looking to work with some state that is stored globally so it can be shared between components and persisted. I have been investigating using a redux store for this. I'm really keen not to "throw the baby out with the bath water" and would like to keep using mobx for the "local" application state, and want to keep the ViewModel separation for the reasons previously outlined.

The approach I have implemented so far is to create the redux state properties as @observable fields as usual in my ViewModel class with the @observable decorator. I then subscribe to redux store state changes (by calling store.subscribe()). When the callback passed to subscribe fires (in a mobx action method), I set the value of the @observable fields on the ViewModel with the value retrieved from state. Mobx is smart enough to only re-render if the value set from state actually changed.

When the component that uses the ViewModel fires componentWillUnmount I call unsubscribe() in order to avoid keeping lots of subscriptions alive when they are not required.

So my ViewModel class looks something like this (just outline code here):

import { Unsubscribe } from "redux";

class ViewModel {
    @observable
    public fieldFromReduxState: string | null = null;

    private _unsubscribe: Unsubscribe | null = null;
    // Called by the containing component in componentDidMount
    public attach() {
        this._unsubscribe = store.subscribe(this._updateFromState);
        // Initial update from state
        this._updateFromState();
    }
    // Called by the containing component in componentWillUnmount
    public detach() {
        if (this._unsubscribe) {
            this._unsubscribe();
            this._unsubscribe = null;
        }
    }
    @action.bound
    private _updateFromState() {
        const state = store.getState();
        // Update local field from state
        this.fieldFromReduxState = state.myreducer.statevalue;
    }
}

and the components that use the viewmodel look something like (again, just outline code):

interface IMyComponentProps {
    viewModel: ViewModel;
}

@observer
class MyView extends React.Component<IMyComponentProps> {

    public static defaultProps = {
        viewModel: new ViewModel()
    }

    public componentDidMount() {
        this.props.viewModel.attach();
    }

    public componentWillUnmount() {
        if (this.props.viewModel) {
            this.props.viewModel.detach();
        }
    }

    // In render, use this.props.viewModel.fieldName
    // Bind events to viewModel methods
    // In this way, the component retains just the pure view functionality
    //  and the ViewModel can be tested / re-used in isolation from react
    ...

}

What I am looking for is insight / advice on those much more experienced with mobx, react and redux as to whether I am likely to hit issues with this implementation, or have been overlooking something key. I'm acutely aware that this is all pretty new to me and I might be creating some nasty issues for myself. I would also be very interested to see if anyone else has come up with good patterns for using mobx together with redux for the different state scenarios in the same project or even component. Seems to me this delivers the best of both worlds with a choice of where the state should live.

I am aware that the idea of moving logic out from the component into a separate ViewModel class is not going to be a favoured approach for everyone, so please humour me a little on that if you can.

I am also aware that many would advocate implementing every single state change (including the results of transient data fetches from a back end) in redux using actions and reducers, but I'm really not keen on going down that route for components without any need for shared state; the mobx model is so simple to work with, particularly for devs who have not got much experience with redux concepts, where a code base is being migrated from earlier technologies such as knockoutjs or there is a general awareness of the MVVM pattern.

So in summary I'm primarily looking for any pointers on pitfalls and problems that my described approach might be leading me into, and any advice on how I might address them if possible.

Sorry for the length of the pre-amble and many thanks in advance for any help or advice offered.

0 Answers
Related