Rethinking of Redux and MobX
remob - is an experiment to improve usability for redux. It is build upon combining simple state management from Redux and effective usability of MobX and decorator power.
In comparing giants like Redux and MobX you can find pros and cons in usage for both sides, but what if we can take pros out of them? As general idea I was wondering if it possible to give Redux more structure and easiness in usage like i felt using MobX but without overwhelmed observables.
Redux is good and gives correct vision of state management, flow of data, consequences of changes. But also from the box it gives such a big amount of options to create your application that could be a bit frustrating. Biggest concerns I would like to change are:
- lot of code to init your
not an array
store- when your app grows enough you can find yourself in situation when you have to create lot of dumb files/code to achieve simple logic and align to your structure.
- action types produces namespace issue and you have to almost duplicate initial state to actions and so on
- switch case need within reducer
- sure here you can use additional
redux-actions
or similar but its additional wrapper
- sure here you can use additional
- small HOR usage
- you cannot just have one reducer and simply reuse it from another one, to have it you need HOR and then use it like HOR everywhere, but in my opinion this should be on top of everything right from start
mapState
&mapDispatch
- i've seen a lot of having this guys near to components even with possibility to have near store, this selectors and actions should just live in store. So store should have ability to combine it
MobX is also a good option for state management with pros and cons.
Usage of classes
and decorators
are just beautiful with combining all needed data inside of instance - selectors, actions, etc.
But using MobX after Redux you probably faced with lack of unidirectional flow, immutability and all pretty things Redux has.
Observables as pattern are good, but predictability and unidirectional flow worth more.
So basically this is the wrapper upon Redux with decorator usage. Enjoy to use it =)
Please add setCombiner
to your code.
Check out setCombiner
doc here.
yarn add remob
import remob from 'remob';
Remob has 8 exports from index file, they are:
- Reducer
- action
- selector
- thunk
- inject
- combineRemob
- connectRemob
- setConnector
- setCombiner
Is a main class to start with. When you want to create new remob you should import Reducer
and extend remob from Reducer
or another remob. Inside of Reducer
live only registrators and reducer implementation.
import { Reducer } from remob;
class Counter extends Reducer {
initialState = { field: 0 };
}
export default new Counter();
To see all actions and selectors that remob registered, you can use
remob.debug()
method - this will console.log
every registered action and selector.
Is a decorator to use within Reducer. You may use it like function @action()
or like statement @action
.
Using this on some function in remob will produce next actions:
- registrate action to remob and reducer
- make this function to be dispatchable
implementation of actionable functions should be like reducer that will be fired only on this action. It takes 2 params as arguments state and action.
action could be an implementation for part of state, for this case you have to pass property name to first param - @action('field')
import { Reducer, action } from remob;
class Counter extends Reducer {
initialState = {
field: 0,
deep: {
field: 42,
}
};
// @action would do the same
@action() increment(state) {
return {
...state,
field: state.field + 1,
}
}
@action('field') incrementField(state) {
return state.field + 1;
}
// both actions will work just equal.
// deep path exist, _.get powered.
@action('deep.field') incrementDeepField(state) {
return state.deep.field + 1;
}
// functions supported, more dynamic for your needed flow
@action(state => (Math.random() > 0.5 ? 'field' : 'deep.field')) changeRandomPart(state) {
return Math.random();
}
}
export default new Counter();
Is a decorator to write selectors from state to components.
import { Reducer, selector } from remob;
class Counter extends Reducer {
initialState = { field: 0 };
@selector incremented(state) {
return state.field + 1;
}
}
export default new Counter();
Is a decorator very similar to action but with one change - it has different dispatchable method and it will call function implementation as argument to dispatch. Usable to create side effects within thunk middleware.
import { Reducer, thunk } from remob;
class Counter extends Reducer {
initialState = { field: 0 };
@thunk decrement(dispatch, getState) {
...
}
}
export default new Counter();
Is a function to inject remobs into component using connect. It takes object of remobs you want to pass, get every action/selector/thunk/state and mapps dispatch and state and them merges to 1 object.
Keys of argument should equal to how you created stores of them and value should be a remob.
import { connect } from 'react-redux'
import { inject } from remob;
import Component from './Component'
import remobImplementation from '../store/remob'
export default connect(...inject({
remob: remobImplementation,
}))(Component)
Is a function to combine remobs for store creating, it takes reducer implementation of every remob passed.
import { combineRemob } from 'remob'
import { createStore } from 'redux'
import stores from '../stores'
createStore(combineRemob(stores))
Make sure you pass uniq names for stores as it will throw an error if you have any duplicate.
For easy connect stores to components use connectRemob
method.
It is simple facade over connector (such as react-redux
provides).
connectRemob
doesn't have default connector so you have to pass your connector.
Please have a look to setConnector
method.
Interface gives ability to pass remobs in 3 ways:
- strings
- remob
- object with remobs
// assume 3 stores already exist named 'store1', 'store2', 'store3'
import { connectRemob } from 'remob'
import store2 from '../stores/store2'
const Component = props => <YourAwesomeComponent {...props} />;
// string store
connectRemob('store1')(YourAwesomeComponent);
// instance store
connectRemob(store2)(YourAwesomeComponent);
// object store
connectRemob({ store4: store2 })(YourAwesomeComponent);
It will automatically get already registered remobs and pass them to component.
Names will be taken from combineRemob
by keys assigned to each remob.
You can override this names by passing object of remobs.
Defines what exact connector will be used on connectRemob
method.
import { setConnector } from 'remob';
import { connect } from 'react-redux';
setConnector(connect);
Defines what exact combineReducers for injection. Setup this on state initiating.
import { setCombiner } from 'remob';
import { combineReducers } from 'redux';
setCombiner(combineReducers);