Redux boilerplate reduction - laziness or not? Switching to MobX


#1

Don’t get me wrong :slight_smile:. I love clear and explicit code and don’t like too much “magic”. But I am lazy and do not want to write/copy-paste/generate ton of constructions, which could be easily packed into something much smaller.

Since I have started using Redux I repeat to myself “what a wonderful thing!”. But this wonderful thing requires me to write rather big potion of boilerplate. I know, that Dan Abramov argues very well, that all this staff is really necessary to understand what is happening and that is not a good idea to simplify the general construction. But for me this is an exercise for a few times and after the understanding of the universe finally enters coders head, the code could be suppressed a bit.

So, I did a small helper to reduce my code. Here is, how it could be used:

import { Meteor } from "meteor/meteor"
import collections from "/imports/collections"
import { createReducer } from "meteor-react-helpers" // <-- this is my helper

/* Tags - create reducer only when subscription is ready */
Meteor.subscribe( "tags", {
    onReady: () => {
        const initialState = {}
        collections.Tags.find().fetch().map( tag => { initialState[ tag.name ] = { ...tag, enabled: true } } )

        createReducer( "tags", {
            add: ( state, tag ) => {
                const newItem = {}
                newItem[ tag.name ] = { ...tag, enabled: true }
                return { ...state, ...newItem }
            },
            toggle: ( state, tagName ) => {
                const tag = state[ tagName ]
                if( !tag ) return state
                const newItem = {}
                newItem[ tagName ] = { ...tag, enabled: !tag.enabled }
                return { ...state, ...newItem }
            },
        }, initialState )
    } // <-- This is the default state
} )

/* Keywords filter */
createReducer( "keywords", {
    set: ( state, str ) => str || state,
    reset: ( state ) => "",
}, "" )

/* Country */
createReducer( "country", {
    set: ( state, str ) => str || state,
}, "" )

/* It is possible to add actions into existing reducer later
   Maybe it is better to rename helper into 'createOrUpdateReducer'
 */
createReducer( "country", {
    reset: ( state ) => "",
}, "" )

Actions, serializable action types are created automatically, new actions added to existing reducers, reducers are combined into one and attached to the only one store, when new reducers are added the combined reducer is replaced with the new one, the redux state is reseted to initial (by default off) when hot-reloading the page - all this is done behind the scene. Actions are namespaced by reducer name and are both exported from the helper and attached to the React context.

https://i.imgur.com/dyEg0bS.png


What do you think about that? Is it just laziness, which leads to the unclear code, or it makes some sense?

Edit: The helper code is here


#2

After making several non-Meteor redux apps, I think the question is who needs access to your code? If it’s just you, and you get it, go with it!

I had your issue. I got so lazy that I abstracted my actions and reducers to the model, not the action. For instance, I had many array of objects (collections) in my Redux state. So instead of FETCH_POSTS, FETCH_USERS, etc I went with FETCH_COLLECTION and sent the array key as a variable. Instead of CRUD reducer functions for every array of objects, I had 4.

Then I got so bad that I abstracted the AJAX call too. The component could say fetchCollection(‘array’, ‘field’, ‘value’, ‘endpoint’, ‘method’), e.g. fetchCollection(‘users’, ‘id’, 1, ‘users’, ‘GET’).

But I’m the only one that needs to know the code, so it worked fine and I’m happy with my decision.


#3

Thanks for sharing your experience!

It seems, you are even more lazy than me :))) I still try to follow the Redux approach, creating reducers for the UI state objects, one reducer per one logical unit. I have read that in general it is not very good idea to use Redux for the data-layer, actually replacing (or duplicating) mini-mongo. I thought about the approach similar to your in terms of implementing the Session alternative on top of Redux, but did not find it useful as it will get most of the current disadvantages of Session/Tracker pair.

So my idea is not the complete abstraction of the data inside Redux, but just to reduce the amount of code, which produces the same set of actions/action-types/reduces/store as I could create manually.


#4

I did the same, actually… I noticed I kept making mistakes with react-komposer containers so I just abstracted all the things!

I also used a simple actions, mutations & getters vuex pattern and a single redux reducer function to modify state… seems to work well and is a lot more straightforward, IMO.

Now my code is cleaner and I only need to update the underlying implementation in one place if I wanted to ¯\ _ (ツ)_/¯


#5

I did something similar with action creators. Got tired of creating so many simple ones everywhere I ended up just creating a general one and passed keys as strings.

Then eventually I stopped using Redux completely. It was getting in the way of productivity and Dan himself said don’t use Redux until you have problems with vanilla React, which I don’t.

Not being able to track reactive data from Meteor in the Redux store was actually a big problem for me. I decided I’d just wait for Apollo to come out before making use of Redux.

If you find the Redux boilerplate more trouble than it’s worth, it could be a symptom that you don’t really need Redux.


#6

@eddyborja, Well, probably you are right in your decision. What I like in Redux is that it allows to keep the app state in the single store and outside the components, all the state changers are outside as well. Redux dev-tools if perfect toy to play with :slight_smile:. Sure, it is possible to use root context the same way, but it does not provide all redux’s fun out of the box.

Where do you keep states, in components or in the context?

As for the reactive data tracking - in my case it will be something like that:

import { actions } from "meteor-react-helpers"

Tracker.autorun( () => {
    actions.country.set( Session.get( "country" ) ) // <-- actions are automatically dispatched
} )

Yes, the state is duplicated both in reactive var and in the redux store, but why do we need reactive var at all?


#7

@reoh, could you, please, provide an example of those simple actions, mutations, getters, single reducer? I am not sure I understood your pattern.


#8

you guys really need to check out https://github.com/mobxjs/mobx, I had the same issues as you guys and mobx does everything redux(except time travel) does but better


#9

@sikanx, thank you for the link. I have read the readme and I think, mobx lacks the major principle of redux - the necessity to dispatch state changers (actions) to mutate the state. Actually, state is not readonly in mobx at all, so it is easy to break the state.


#10

But do you know why the state has to be immutable(read-only) to begin with? It is because there is no way to compare before/after state, and because of the fast === comparison allowing for re-renders in componentShouldUpdate().

That is not a problem with mobx because the values are being observed, and the components that watches those values are updated without need to do any checks like redux connector.

Also in Mobx the components that are responsible for that data is only rerendered. So the benefit of a immutable store quickly vanishes there while we get much more cleaner and concise code.

Additonally, Mobx has actions that allows you to specifiy mutations, and wraps every mutation inside it in a transaction( so it is updated all at once for multiple mutations), enables console logging with mobx-devtool, all without the need to write unessesary boilderplate actiontype codes like redux does.

https://mobxjs.github.io/mobx/refguide/action.html

Also, even the creator of redux took notice of mobx and praised it. https://twitter.com/dan_abramov/status/703649627065679872


#11

I don’t say mobx is bad or something. It has different philosophy, which leads to different set of pros and cons. And it is (from the first sight) much more “magical”, so redux seems to provide better predictability. As for the boilerplate - my helper allows me to get rid of it almost completely while keeping the immutability benefits, so I’ll stay with redux for a while :slight_smile: Just an opinion.
Anyway, thanks for sharing, it is always important to know other options.


#12

After some digging into MobX I have found, that it perfectly fits all the needs! My concerns regarding the permissiveness were not confirmed - MobX now has strict mode, which does not allow to change the state out of the action. @sikanx, thank you again, you saved me a lot of time in the future :slight_smile:

Here are some relevant links, for those who, like me, never met MobX before:


#13

@priezz/@sikanx - did you guys get this working in the end. I tried, but seem to have some problems.

(1) Added

npm install --save babel-core babel-loader babel-plugin-transform-decorators-legacy babel-preset-es2015 babel-preset-react babel-preset-stage-1 babel-register
npm install --save-dev babel-eslint

(2) .babelrc

{
  "presets": ["es2015", "react", "stage-1"],
  "plugins": ["transform-decorators-legacy"]
}

(3) I can set observables, but observers seems to not work with react/meteor… :frowning: not sure why…

tat


#14

Ok - this seems to work. Am I using this correctly?

import React from 'react';

import { observable } from 'mobx';
import { observer } from 'mobx-react';

import { CardTitle } from 'material-ui/Card';

import IconButton from 'material-ui/IconButton';
import ActionLockOpen from 'material-ui/svg-icons/action/lock-open';
import ActionLock from 'material-ui/svg-icons/action/lock';

@observer
export class Q6ATitle extends React.Component {
  @observable canEditNew;

  handleClick() {
    this.canEditNew = !this.canEditNew;
    this.props.output(!this.canEditNew);
  }

  renderTitle() {
    if (this.props.lock) {
      return (
        <span>
          {this.props.label}
          <IconButton onClick={this.handleClick.bind(this)}>
            {this.canEditNew ? <ActionLockOpen/> : <ActionLock/>}
          </IconButton>
        </span>
      );
    }

    return this.props.label;
  }

  render() {
    return (
      <CardTitle title={this.renderTitle()}/>
    );
  }
}

Q6ATitle.propTypes = {
  label: React.PropTypes.string.isRequired,
  lock: React.PropTypes.bool.isRequired,
  output: React.PropTypes.func,
};

It should be pointed out that i don’t really understand how to use stores. So in essence I am using Mobx to handle bits of UI state.

(1) I don’t have a constructor of any kind. How would i handle stuff that has to be instantiated on load without the component methods?
(2) Just pass in as props and change my class to a stateless component?
(3) Can someone explain how to use stores (like I’m 5 please)
(4) This seems like a dodgy implementation. It seems to me like i should pass in an observable prop (canEditNew above), and then the Q6ATitle component should just be a stateless component function. Thoughts?

Tat


#15

I do not think it is reasonable to keep an observable within the observer class. You will get the same result just by using the component state functionality. The idea is to split data and the interface, so I operate with the following chain: observable data structures -> observer containers ("smart" components) -> "dumb" components.

Hint: You can use arrow functions for methods definitions, in this case you will not have to to .bind(this).


#16

i hear what you are saying, but can’t agree. I know that the rage is all ‘smart’ and ‘dumb’ presentational components which are pure and functional etc. The problem is I am not proficient enough in JS to pull this off. The reason I am using Mobx is precisely to get away from the component state functionality.

Component state functionality handling is a massive pain in the rear, and doing a full redux solution is vast amounts of boilerplate. Mobx just handles it for me… stick an observable on it and it’ll render as it changes. Its like magic.

So i use observables and observers to handle all state within my components. If i need to load something up on first render or sometime in the middle or end etc, the componentWillMount etc lifecycle stuff comes in very handy.

Thanks so much.

Tat

tl,dr: understand what you are saying, but too noob to execute…


#17

Using mobx for component state is more than fine, in fact it is better than setState because it is synchronous.


#18

I just wonder why we don’t use createContainer which created by Meteor and maintained by MDG.
I using it and love it.
The problem is we love redux but we can’t use both as well. So let create some thing that use Redux concepts.


#19

That makes sense! However, if I deal with setState from within componentWillReceiveProps I have no side effects, described in the article. Well, setTimeout hack is to be used for some external components, which use the state “wrong” way.


#20

BTW can anyone explain how to put meteor data to mobx store and observe changes?