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...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.
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.
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.
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.
- 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".
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(...));Also I use my json-mobx library to get serialisation and undo/redo. Example: https://github.com/danielearwicker/baltar
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.)
MobX makes it very easy to do that inspection between states.
Don't look at mutable state as evil. Redux state is mutable too.
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.
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.
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.
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.
https://www.npmjs.com/package/tiny-state-manager
Maybe not for prod, but certainly great for simple uses and a quick start :)
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 :)
Well, that would be much worse, don't you think?
My point was that having to keep identifiers to everything is silly.
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.
1: Implementing Redux is tedious. But it doesn’t have to be. https://medium.com/@jeswin/implementing-redux-is-tedious-but...
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.
"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.
It has delegated properties which are wonderful for redux-style architectures.
How do you deal with the massive size of the javascript that is generated by kotlinJS?
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?
Not to mention it's heavily decorator based, which makes things a _lot_ more complex and inter-dependant. No thanks.
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?- 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?
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.
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.
https://www.joelonsoftware.com/2001/04/21/dont-let-architect...
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?
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):
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.