
Show HN: Kea – The power of Redux with the simplicity of MobX - mariusandra
https://kea.js.org
======
kasbah
Looks pretty neat. My initial reactions:

\- I don't like the `@kea(...` because it's another bit of JS syntax I hadn't
heard of before. Though I do like decorators in Python, I just feel like I
have learned enough of JS syntax to be productive and they seem to keep adding
unnecessary stuff.

\- There is still duplication between the action names and the reducers. When
using Redux I now always use these two functions:

    
    
        function makeReducer(reducerObject) {
          return (state, action) => {
            if (Object.keys(reducerObject).includes(action.type)) {
              return reducerObject[action.type](state, action.value)
            }
            return state
          }
        }
        
        function makeActions(reducerObject) {
          const actions = {}
          Object.keys(reducerObject).forEach(name => {
            actions[name] = value => {
              return {type: name, value}
            }
          })
          return actions
        }
    

So I can just declare a single object of reducers and can infer the action
names and creators from that e.g.

    
    
       const reducerObject = {
          increment(state, value) {
             return state + 1
          },
          decrement(state, value) {
             return state - 1
          }
       }
     
       const reducer = makeReducer(reducerObject)
       const actions = makeActions(reducerObject)

~~~
spankalee
I'm a big fan of decorators, but it seems like much of the React/Redux world
is overly class-decorator happy. It's really common to define properties in a
decorator which would be much easier to read, IMO, as static or instance
properties. I'd rather read something more like this:

    
    
        @kea // kicks off the meta-programming to read the statics below
        export default class Counter extends Component {
          static actions = {
            increment: (amount) => ({ amount }),
            decrement: (amount) => ({ amount }),
          };
    
          static reducers = {
            counter: [0, PropTypes.number, {
              [this.actions.increment]: (state, payload) => state + payload.amount,
              [this.actions.decrement]: (state, payload) => state - payload.amount
            }],
          };
    
          static selectors = ({ selectors }) => ({
            doubleCounter: [
              () => [selectors.counter],
              (counter) => counter * 2,
              PropTypes.number,
            ],
          });
    
          render () {
            const { counter, doubleCounter } = this.props
            const { increment, decrement } = this.constructor.actions
    
            return (
              <div className='kea-counter'>
                Count: {counter}<br />
                Doublecount: {doubleCounter}<br />
                <button onClick={() => increment(1)}>Increment</button>
                <button onClick={() => decrement(1)}>Decrement</button>
              </div>
            )
          }
        }

~~~
mariusandra
Hey, thanks for the feedback! This is definitely something to think about, as
nothing in the syntax of kea is set in stone.

That said, the magic with having a big options hash in the `@kea({})` call
itself is that then you can easily disconnect the logic from your components
as your application grows. Here's more information about this:
[https://kea.js.org/guide/connected](https://kea.js.org/guide/connected)

Also, I'm not sure you can do `this.actions` inside a static variable, but I
understand the point that you were trying to make.

~~~
spankalee
> That said, the magic with having a big options hash in the `@kea({})` call
> itself is that then you can easily disconnect the logic from your components
> as your application grows. Here's more information about this:
> [https://kea.js.org/guide/connected](https://kea.js.org/guide/connected)

Interesting. I still think these are better modeled (and read) as part of the
class declaration. You can do the same thing with mixins:

Transforming that example:

    
    
        import FeaturesLogic from '../features-logic.js';
    
        @kea
        class CounterExampleScene extends FeaturesLogic(Component) {
          // no change here
        }
    

Where FeaturesLogic is a JS class mixin:
[http://justinfagnani.com/2015/12/21/real-mixins-with-
javascr...](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-
classes/)

    
    
        export const FeaturesLogic = (base) => class extends base {
          static actions = ...;
        }
    

> Also, I'm not sure you can do `this.actions` inside a static variable, but I
> understand the point that you were trying to make.

Not in the current Babel transform, but I thought it may be in the latest
unified class fields proposal. I'm digging for it...

------
pavlov
I know it's just an example... But the Slider code is ridiculously
overwrought.

Building this type of image carousel is trivial with just plain HTML+CSS+JS.
This example uses "actions", "reducers", "selectors", weird functions from
something called "redux-saga/effects", a "promise-based timeout" library AND
generator functions to produce the magic ... of switching between images.

If someone I work with came up with this code, I would assume that the person
is extremely bored with her job and wants to spruce up her résumé with
buzzwords.

~~~
mariusandra
Hey, indeed it's an example, as you point out. This was the best example I
could come up with that demonstrated most of the functionality available, yet
remained rather small in size.

The point was to show what this library is capable of, so that people with the
imagination of your imaginary co-worker could extrapolate its usefulness to
real world applications.

I have built 3 large applications using kea, and at that scale all these
features come in handy.

~~~
pavlov
Just to clarify, I'm not picking on you; I realize it's hard to come up with
meaningful examples.

The problem with Slider is that it doesn't readily extrapolate into anything
bigger. To put it another way, it's not the embryo of any real world web app.

I feel this indicates a larger problem with present-day web dev: due to the
complexity of client and server stacks, it's really hard to come up with a
self-contained example app that makes any sense as a starting point.

Looking back some 20 years, one could look at the example apps from the Win32,
Classic MacOS and OpenStep SDKs, and come out with a solid feel for what it's
like to build an app on each platform.

Today, I could compare the provided examples from twelve different JavaScript
frameworks, and I wouldn't feel any closer to understanding their strengths.

------
north
Shameless plug for another take on the same promise:
[https://github.com/jnorth/megalith](https://github.com/jnorth/megalith)

I think most people who are attracted to Redux's core principals but want a
more opinionated structure will end up gravitating towards mobx-state-tree
though.

~~~
arstin
If my favorite part of redux is structuring flows with redux-saga, does mobx-
state-tree have a solution for me?

For example, this is nice:

    
    
      // On login, spin off the auth process. If an error or a logout happens, clean up.
      // Be sure to cancel any pending auth request! 
      // Then get ready for a new login.
    
      function* loginFlow() {
        while (true) {
          const {user, password} = yield take('LOGIN_REQUEST')
          const task = yield fork(authorize, user, password)
          const action = yield take(['LOGOUT', 'LOGIN_ERROR'])
          if (action.type === 'LOGOUT')
            yield cancel(task)
          yield call(Api.clearItem, 'token')
        }
      }
    
    

(Genuine question...idk!)

~~~
north
This example doesn't cover all the cases you're showing above, but in megalith
or MST you would typically have a clearer separation between the functions
that change state and the async communication parts.

[https://github.com/jnorth/megalith/blob/master/docs/examples...](https://github.com/jnorth/megalith/blob/master/docs/examples/async-
actions.md)

[https://github.com/mobxjs/mobx-state-tree#creating-async-
pro...](https://github.com/mobxjs/mobx-state-tree#creating-async-processes)

~~~
arstin
Yeah, I also like to use redux with a clear separation between actions that
change state and those that don't. (For example, reducers only take actions
prefixed by DATA_ or UI_, all other actions...the bulk of them really...don't
have anything to do with changing state and are handled by other processes.
But because of this I don't bother with action creators, so what do I know!)

A conceptual appeal of mobx to me is that this distinction seems baked in. The
original mobx didn't click with me like redux did ¯\\_(ツ)_/¯, but if MST gives
transactional updates and support for redux dev tools (right?) could be worth
trying.

~~~
north
Right, maybe I called out the wrong part of your example. redux-saga gives a
really elegant way to describe the flow, but I feel at the expense of wrapping
the async code with special helpers (call, put, fork, etc). You have to learn
a few extra concepts for it to be comfortable. Async code in MST and megalith
interact a bit more directly with the stores.

One thing I tried to do with megalith is mapping ideas from Redux to built-in
language features as much as possible. An action object can trivially be
represented as a function call for example—action name and payload is mapped
to function name and arguments.

I think Mobx gets into trouble there by building off of observables—you have
to litter your async code with different types of action markers to keep state
consistent. MST tries to corral all those variations down into a smaller set,
where the Redux ecosystem is layering ideas on top of an small initial core.
Megalith and MST are starting from two completely opposite sides, but end up
looking very similar somewhere in the middle :)

------
burntcaramel
I’ve made a lightweight React library influenced by functional setState, and
supports async/await/Promises, extracting values from DOM / forms easily, pure
function actions, animation with requestAnimationFrame, and reloading when
props change.

I’ve put a lot of effort into providing some good examples (more coming!), and
also on keeping it lightweight: core is 1.18KB gzipped.

[https://github.com/RoyalIcing/react-
organism](https://github.com/RoyalIcing/react-organism)

Feedback welcome, here or as issues!

~~~
armandososa
Looks nice, but I think I can get most of it using recompose, which I've been
using extensively lately.

~~~
burntcaramel
Yes, you would be able to do a lot with the flexible recompose. I’ve tried to
make something more friendly and lightweight that makes common use cases
easier. I can find recompose a little overwhelming in the multiple layers.

------
kevincennis
Funny, I always thought of Redux as offering greater simplicity and MobX being
more powerful.

~~~
mariusandra
With great simplicity comes great power ;)

MobX is simple to get going, but I fear there are dragons beneath. Here's a
small writeup I did about it on Reddit:
[https://www.reddit.com/r/reactjs/comments/6pni2i/kea_high_le...](https://www.reddit.com/r/reactjs/comments/6pni2i/kea_high_level_abstraction_between_react_and_redux/dkr97n4/?st=j5lgq1hz&sh=4ba21f1e)

------
Ciantic
Is this type-safe? Can it infer the payload type somehow? With MobX you can
write type-safe code, and it's pretty important in bigger code bases. Redux
can be made type-safe but is a bit clunky to use that way.

~~~
mariusandra
Hi, propTypes are supported as an (optional) first class citizen. Thus
whatever is the output of a reducer or a selector, provided it is passed to a
react component, is type checked.

That said, I haven't used flow or TS myself and can't confirm nor deny
compatibility

------
dpnewman
The concept expressed in the title matches a desire. Looking forward to
checking out and hearing other's experiences.

------
OliverLassen
Why not just use state, which seems like the correct solution to the examples?

~~~
mariusandra
Because the examples are just that, examples. Kea is designed for large
complicated apps, and is in production use for at least 4 apps that I'm aware
of (from marketplaces to fleet tracking)

------
33degrees
I have to dig into this but at first glance I like the approach

