Hacker News new | past | comments | ask | show | jobs | submit login

If you're looking for something easier to use to help you manage state in your React apps look no further than Mobx. It's pretty incredible how stupid easy it is to use, it kind of feels like cheating.

https://stackshare.io/mobx

I've tried to use Redux a couple of times but I just spent way too much time in plumbing code. Code I really don't care about. To be frank this code looks terrible (no fault of the author):

    const handlers = {
      [CREATE_NOTE]: (state, action) => { ... },
      // ... a thousand more of this
    }
Not to mention, I never not once felt happy working with Redux. I'm all about developer UX and working with tools that feel nice to use.

With Mobx you just declare a variable as Observable, then mark your components as Observers, and voila: You have crispy, no-plumbing reactivity.

In a way it kind of feels like Meteor where you save data on the database and it replicates everywhere it's being used.




I never understood why a lot of this was never handled with metaprogramming. I feel like it might have something to do with Redux using constants and imports as a type safety hack, but all the ceremony drives me nuts.

First we enumerate our actions as constants...

    const ADD_TODO = 'ADD_TODO'
Then we need to create an "action creator" somewhere:

    import { ADD_TODO } from '../actionTypes'

    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
Then we need to import that action and dispatch it when we need it...

    import addTodo from './actions'

    dispatch(addTodo(text))
Now even the same nonsense for reducers...


I'd highly recommend using a type system like Flow to enumerate the shape of possible actions the store should support. This brings numerous advantages:

1. You can use strings for action types, no more need to import constants.

2. No need for action creators for simple actions, you can just create the action object inline and Flow will validate its shape.

3. You can be sure that reducers are unpacking properties that actually exist from the actions.

In other words, your example could simply be:

    // types.js
    type Action =
      | { type: 'ADD_TODO', text: string }

    // TodoList.js
    dispatch({ type: 'ADD_TODO', text })
...and Flow would confirm at 'compile time' that it matches the contract dispatch expects.

This approach has scaled up extremely well for me and no longer feels verbose.


I agree with that. When I first started with Redux and Flow, I was just following all the examples and made sure I used action creators for everything, even if there was no payload. Now I'm feeling more confident with Flux, and I recently realized that no, I don't need "action creators" for everything. That's silly. It's OK to just dispatch an object using your action constant directly.

And then I found myself writing action creators on purpose, not because I saw it in some tutorial, but because it actually does remove some duplication.

This was also missing from my education for a long time: https://github.com/acdlite/flux-standard-action#actions

I had no idea why I was using a "payload" key, instead of just putting all the values in the top-level object. But this convention makes a lot of sense now.


And most of that came from Flux to begin with (as seen in https://facebook.github.io/react/blog/2014/07/30/flux-action... ). There's also a lot of interesting parallels with the Win32 WndProc functions, as discussed in https://bitquabit.com/post/the-more-things-change/ and https://bitquabit.com/post/the-more-things-change/ .

That said, you are absolutely encouraged to abstract over that process as much as you want! For example, the https://github.com/acdlite/redux-actions library will generate both the action constants and the action creators for you, and there's many more similar utilities.


Whoops, just noticed the bad copy-and-paste. Meant to link to the HN discussion for that article: https://news.ycombinator.com/item?id=10381015 .


Speaking as someone working in a large Redux codebase, I agree with you 100%. The amount of boilerplate required to do almost anything is staggering.


Personally, I find that when boilerplate gets out of control, you can do one of two things, probably in this order:

1. Write helpers. 2. Write middleware.

The first is zero magic, and allows you to opt-in to abstractions as needed. The latter is more powerful and can remove pretty much any boilerplate, but you have to be careful about introducing too much magic.

Redux isn't much different than any other code you might write. If you prematurely abstract, you could create a leaky abstraction, and you'll have to unroll the whole thing. If you don't abstract at all, and copy-pasta everything, you're also going to have a bad time.


Dan Abramov has said that the Redux docs were written in a deliberately verbose style to get across the ideas, and he didn't really expect people to strictly imitate that style. There's a wide variety of approaches and utilities you can use to abstract things as much as you'd like - that's entirely up to you.


As a beginner in Redux, can you tell me about the other ways to handle these boilerplate?


A few possible options:

- Put all the action types, action types, and reducers for a given portion of the application into one file. This is known as the "ducks" structure, per https://github.com/erikras/ducks-modular-redux .

- Use a utility library like https://github.com/acdlite/redux-actions to generate action types and action creators for you

- Write middleware to manage common logic for your application such as API calls

- Skip writing action creators and declaring action constants, and just have components do `this.props.dispatch({type : "SOME_ACTION"})`.

I don't recommend the last approach, but it's _totally_ up to you, especially because what you regard as "too much boilerplate" is going to be a personal opinion. One person's "too much boilerplate" is another person's "explicit and easy to trace through the system".


I usually handle this via text editor macros when I'm starting off

Start with

actionNames.txt

  ADD_TODO
  REMOVE_TODO
Cat that file to actionTypes.js and actions.js. In actionTypes.js, expand:

<line> to:

  export const <line> = "<line>"
And in actions.js, expand <line> to something like:

  export const (camelcase-string <line>) = () => {
    return {
      type: types.<line>,
    };
  };
Also instead of importing actions and actionTypes individually, I'll generally do:

  import * as actions from './actions';
  import * as types from './actionTypes';
  dispatch(actions.addTodo(...));


I developed a kind of meta programming pattern and supporting library, based on Redux, called Immuto, to get absolute and complete static typing (via TypeScript) and minimal boilerplate, also referentiality and modularity (cursors that accepted actions). It was fun, but ultimately pointless, as it was never as straightforward as using the automatic observable approach that I'd learned from Knockout.js. Then I heard about MobX (which is the same ideas from KO but better implemented and without any quirky UI templating thrown in) and so now I used that.

Also I use my json-mobx library to get serialisation and undo/redo. Example: https://github.com/danielearwicker/baltar


MobX is really cool, and I wouldn't try to convince anybody not to use it if it works for them. I happen to be pretty religious about immutable data, having been bitten by problems with shared mutable state so many times. I know MobX has actions which help a lot, but I still like the core simplicity of Redux, even if it means more typing.

My dream would be to have David Nolen (of ClojureScript) and Michel Weststrate (of MobX) do a panel where they discuss the essential differences and pros and cons of each approach. The philosophies seem at odds, but I think there's probably a core truth that neither side has fully reached.

Also, I think the core lacking thing of Redux is that it isn't really composable like React. I'm really curious to check out https://github.com/FormidableLabs/freactal since it aims to be a fractal (composable) alternative.

(And yes, props to Elm for having a built-in composable architecture.)


There is no immutable state, at best we have immutable data. Benefit of immutable data is you can inspect two different states when state transitions from state A to state B. You get time travel and all that with this basic concept.

MobX makes it very easy to do that inspection between states.

Don't look at mutable state as evil. Redux state is mutable too.


In my opinion mutable state is most often better than immutable state. In JavaScript, that is, I have a different opinion on the JVM.

The important point is that the state is _serializable_, meaning it doesn't contain side-effects. That also means NetworkRequests must be replayable.

If your state is serializable, you get immutability out of the box by serializing everthing to JSON and back again.


I've tried hard to like Redux as I have to work with it, but I can't.

At first I was perplexed by the boilerplate and the awkward way it seemed like I had to update 5 files just to toggle a view element -- but I figured I just didn't understand yet and one day when I did it would all make sense.

Well... after about a year of working with it, I'll admit I understand it and there is some sense to it, but there really is nothing elegant or likeable about it.

I too have never felt happy working with Redux.

I've got a side project I've been working on and was planning to use Redux since that's what I use at work, but I think I'll take this as an opportunity to try out something else.


While it wasn't really the original point, Redux picked up at a time where Angular 1 had a near monopoly. Redux got mindshare because it was the opposite. Do away with the magic, have a predictable flow, and virtually no framework. Just a design pattern where everything is explicit.

It got really popular because a lot of people want that. Typing is cheap. If a bit of boilerplate and a couple of files drastically affect your productivity, you're not solving a hard problem (and if you're not solving a hard problem, which is the common case, there are a lot of other tools better suited for it).

But because it got popular, everyone jumped on the wagon, including people who were not the target audience, or were not solving problems it was a good tool for. Now, people try to force Redux into being something else, and it really, REALLY sucks at that.


My suggestion is to try Mobx and write your code in TypeScript. It's a different concept when you first look at it but then you'll see it's actually mostly the same as Redux but with less boilerplate code.


I totally agree. When working on React, Redux drove me insane. The way it expects you to wire actions/data is completely stupid in my opinion.

It took me a bit of time to fully understand MobX and I think it's still rough on the edges but its modelisation and usage make so much more sense.

MobX is an elegant solution compared to Redux's brute forcing approach.


There are several state managers on npm, some very simple and small, as an alt to redux (or even mobx)


https://www.npmjs.com/package/tiny-state-manager

https://www.npmjs.com/package/tiny-state-manager

Maybe not for prod, but certainly great for simple uses and a quick start :)


You see something is flawed in Redux at the point you have to pass strings (uppercase constants defined somewhere) around, import them in every file, pass them as identifiers of what you should do with each data.


You don't actually have to do this if it bothers you. You can just use the strings inline, but the author (and many of those using Redux) finds it helps to reduce errors, by instantly pointing out typos.

There are also patterns to reduce the import issue. For example, https://github.com/erikras/ducks-modular-redux. I prefer defining my actions with my action creators in one big index, but the point is that there is absolutely a way to mitigate this concern if it bothers you :)


> You can just use the strings inline

Well, that would be much worse, don't you think?

My point was that having to keep identifiers to everything is silly.


The point of keeping identifiers is to show you the intents of an action. If any identifiers seem silly to be added then maybe they don't deserve to be going through reducers in the first place.


Use Symbols instead of Strings: https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols...

See the second line of the code snippet under "Symbols are completely unique…"

Unless I have misunderstood the spec, to compare symbols you require both the reference and value. Much more preferable than passing around Strings.


You _can_ use Symbols for `action.type` in Redux, but that's discouraged, because they're not serializable and thus will not work properly with time-travel debugging. See http://redux.js.org/docs/faq/Actions.html#actions-string-con... .


if you use redux-actions your reducer can use an action reference directly- not its "type" string. developing this way I don't need to store references to these "constants" and they're only ever relevant when logging.


Why is it flawed to use the module system to enforce references?


Here[1] is my take on a vastly simpler redux pattern, which hides both reducers and containers from application code, while at the same time requiring no middleware for async. It has worked wonderfully for our medium+ sized project.

1: Implementing Redux is tedious. But it doesn’t have to be. https://medium.com/@jeswin/implementing-redux-is-tedious-but...


Mobx is really nice. For a quick and easy database for an Electron app I added a Mobx observer that persists the state to a json file which is read back at startup. It was just a few lines of code and you get seemless persistence for free.

I've expanded on that idea for another Electron app I'm working on (http://www.serverwrangler.com) that encrypts the data before saving and has a database "server" in the main process so data is shared between multiple Electron windows of the same app.


You should definately look at RxDB when you need a persistent reactive store.


I'm eagerly awaiting mobx-state-tree. Basically the best of both worlds.

https://github.com/mobxjs/mobx-state-tree


I can't​ agree more. After using both there is no question that, for me, mobx is better. It is more intuitive and has much less boilerplate code. I've tried to understand why redux is chosen over mobx in some projects, and at this stage I feel it is mainly just momentum and visibility/marketing.


Since you mention Meteor. Why not just use Tracker? I feel that's such an underrated package. It's quite similar to mobx and it's been around for years. Combine it with minimongo and you've got a really flexible reactive state management.


Can we use it outside of Meteor?


I feel you're glossing over the strengths of Redux and how utterly ugly MobX code looks.

Not to mention it's heavily decorator based, which makes things a _lot_ more complex and inter-dependant. No thanks.


Mobx isn't ugly at all. Are you using decorators? You should.

Example:

    import { observable } from "mobx";
    export default class TodoStore {
      @observable todos = [];
      constructor(props) {}
    }

    // Elsewhere in a different file, now you just use it.
    import TodoStore    from './stores/todos_store.js';
    import { observer } from 'mobx-react';
    @observer
    export default class Todos extends Component {
      constructor(props) {
        store = new TodoStore()
      }

      addTodo = (todo) => {
        store.todos.push({ id: 1, text: "Get Milk" });
      }

      render() {
        return (
          // Just iterate over `store.todos`
        )
      }
    }
As you can see you just use the variables and get stuff for free out of the box with no plumbing code except `@observable` and @observer`. What's not to love?


> What's not to love?

For me? Basically all of that. Also now that code is highly dependent on the framework and tightly coupled.

Observables have their place for sure, I like them as a concept actually, but for me MobX is encoding an anti-pattern I try to avoid.


Be careful you don't become an architecture astronaut though. At some point you have to stop working on plumbing and actually solve the business need.

https://www.joelonsoftware.com/2001/04/21/dont-let-architect...


Indeed, a good point to be aware of generally (though I would say we as a group go too far the other direction most times).

But the issue here with that is that I can solve the business needs in React+Redux trivially without running into the architectural problems I mentioned in MobX.

So why on earth would I chose the inferior option?


Some questions already arise from that example:

- How do two separate components access the one store, singletons?

- If two components observe the same store but render different attributes of it are they both updated when the store is updated?

- Are you limited to modifying your store only in your component?


> How do two separate components access the one store, singletons?

You can use <Provider> and @inject to put a store onto the component tree and pull it off whenever you need it. It's just a quick wrapper around the React context API.

> If two components observe the same store but render different attributes of it are they both updated when the store is updated?

Yes, this is just how Mobx works. Observations are set up implicitly. You can observe any @observable value or @computed function just by referencing it within an @observer.

> Are you limited to modifying your store only in your component?

Nope, this works with just plain JavaScript. The main library doesn't offer any React-specific bindings (like Redux), so it can be used with anything.


So much FUD


I swear, there are some users on HN (like yourself) that think FUD is some kind of magic pixie dust to invalidate someones statement. I thought we grew out of that in the early 2000s.


https://github.com/mobxjs/mobx/blob/gh-pages/docs/best/react...

"MobX usually reacts to exactly the things you expect it to. Which means that in 90% of your use cases mobx "just works". However, at some point you will encounter a case where it might not do what you expected. At that point it is invaluable to understand how MobX determines what to react to."

I'm going to be tossing and turning tonight trying not to dream about libraries that usually work 90% of the time.


This isn't about working only 90%. It means that it fits your current thinking to 90%. Only in 10% of your use cases do you have to dig deeper and actually understand how MobX works.


This was a similar promise with the Angular digest cycle. I'll take verbose boiler plate over that any day.


It actually takes 30 minutes to understand how MobX works, and its really simple. I highly recommend it.


I write most my code in Kotlin nowadays, which can compile to JavaScript, or drive react-native on Android.

It has delegated properties which are wonderful for redux-style architectures.


Can you share a link to an example?

How do you deal with the massive size of the javascript that is generated by kotlinJS?


Unfortunately not, since those apps are all business internal.

On Android/react-native, the Kotlin parts runs on the JVM directly. I haven't released a KotlinJS-application to the wild yet, but I don't suppose the size is a problem after running it through webpack, uglify et. al.

You are right, the runtime is rather larger though. Do you have suggestions?


MobX seems to encourage you to mutate your global state inside your components. Isn't that code smell?


I'd take any amount of boilerplate over the hand-rolled flux-ghetti code I'm dealing with now.


but the OO api is a bummer, give us the Clojure Atom api!!! vamos reframe!




Applications are open for YC Winter 2020

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: