Define your Redux actions and reducers using object literal or ES6 class syntax. Also, a handy method for working with side effects.
import * as actionObject from 'redux-action-object';
// Model
class TodoModel {
todos = [{ // Class properties are used as the initial state
...
}];
add(text) { // That's reducer
return {
todos: [
...
]
}
}
}
// Bootstrap code
const { actionCreators, reducer } = actionObject.split(new TodoModel());
const store = createStore(reducer);
const actions:TodoModel = actionObject.bind(actionCreators, store.dispatch);
// UI
@connect(state => ({ todos: state.todos }))
class TodosComponent extends React.Component {
handleSave(text) {
actions.add(text); // Actions are already bound to the store
}
}
When using conventional Redux code related to actions become spread across different places:
- Switch in reducers
- Action type constants
- Action creators
So when you add a new action or modify existing one you need to make changes in at least 3 different places.
Redux-action-object helps this by enabling you to simultaneously define actions and reducers in the same code constructs, using ES6 classes.
You start with creating a class for you model. The class may contain properties and methods. Class properties are used to define the initial state. Member functions are used to define reducers and actions:
class TodoModel {
todos = [{
text: 'Use Redux',
completed: false,
id: 0
}];
add(text) {
return {
todos: [
...this.todos,
{
id: this.todos.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: text
}
]
}
}
}
Instance of this model class is split into Redux action creators and reducer:
import * as actionObject from 'redux-action-object';
const { actionCreators, reducer } = actionObject.split(new TodoModel());
actionCreators is an object with property keys equals to the method names in the model. Property values are Redux action creators. Action type will be equals to method name. For the code above, actionCreators will be:
actionCreators = {
add: function(...arguments) {
return {
type: 'add',
params: [].slice.call(arguments)
}}
}
}
Then you create a store and bind action creators to it
const store = createStore(reducer);
const actions:TodoModel = actionObject.bind(actionCreators, store.dispatch);
Now, you are ready to use it in your components:
@connect(state => ({ todos: state.todos }))
class TodosComponent extends React.Component {
handleSave(text) {
actions.add(text); // Actions are already bound to the store
}
}
In order to completely define actions within classes, redux-action-object provides a way to define side effects within reducer code.
import * as actionObject from 'redux-action-object';
add(text) { // reducer function
actionObject.sideEffect(function() {
localStorage.todos = JSON.stringify(this.todos);
});
return {
todos: [
...
]
}
}
Side effect function will be called after executing reducer. Inside side effect, this will be pointing to actual store state. It means that the side effect can use reducer results, like in the above example.
To enable side effects you need to use actionObject.withSideEffects while creating Redux store:
import * as actionObject from 'redux-action-object';
const store = compose(
actionObject.withSideEffects,
// your other enhancers
)(createStore)(reducer);
Side effects can also be used without the classes:
function todoApp(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
actionObject.sideEffect(function() {
localStorage.todos = JSON.stringify(this.todos);
});
return {
todos: [
...
]
}
default:
return state
}
}
let model = {
a: false,
someAction: () => ({ a: !this.a })
};
let model = {
a: 1,
b: { c: 5 },
topLevel: val => ({ a: val, b: { c: 6 } }),
nested: {
inner: val => ({ a: val, b: { c: 6 } })
}
);
Actions can be reference as expected:
import * as actionObject from 'redux-action-object';
const { actionCreators, reducer } = actionObject.split(model);
const store = createStore(reducer);
const actions = actionObject.bind(actionCreators, store.dispatch);
actions.nested.inner(3);
import * as actionObject from 'redux-action-object';
let model = {
a: 1,
findUserRequest: actionObject.creator(() =>
fetch('/api/findUser')
)
};
or
import * as actionObject from 'redux-action-object';
class Model {
a = 1;
@actionObject.creator
findUserRequest() {
return fetch('/api/findUser')
}
}
Full example is located in todo-demo.js file. To start it, run
npm start
and the open
http://localhost:3000/
Feel free to create an issue or send a PR
MIT. See LICENSE