It's a tool to organize your reducers and action creators, built on top of Redux.
This module ships ES6 code, so be sure that you're using tools like Babel or your target browsers supports ES6 natively.
We used to organize our action creators and reducers in separate files, but on large scale apps this approach doesn't work very well. Tons of separate files and constants with action names brings bunch of pain for developes. Approach of Processor is to merge all of these files.
Reducers in Processor are separated by chunks of state and it allows you to store initial state, action creators and reducers all together.
Simple as hell
npm install redux-processor -s
or, if you're familiar with Yarn
yarn add redux-processor
Processor itself provides two functions to create state chunks:
processor(initialState, actions)
– processor functionality itself
processorMulti(multiActions[, initialState])
– for those, who's familiar with combineReducers
Both ES6 modules and CommonJS styles are supported, so you're free to choose you the way to import Processor:
import { processor } from 'redux-processor'
import { multi } from 'redux-processor'
/* OR */
import { multi as processor } from 'redux-processor'
/* OR */
import processor from 'redux-processor/lib/processor'
import multi from 'redux-processor/lib/multi'
const processor = require('redux-processor').processor
const multi = require('redux-processor').multi
/* OR */
const { processor } = require('redux-processor')
/* OR */
const processor = require('redux-processor/lib/processor');
const multi = require('redux-processor/lib/multi');
Simple example of how to use processor within your application:
import { createStore } from 'redux';
import { processor } from 'redux-processor';
const initialState = {
count: 1
};
const preducers = processor(initialState, {
increase: {
type: "INCREASE",
process(state){
return Object.assign({}, state, {
count: state.count + 1
})
}
},
decrease: {
type: "DECREASE",
process(state){
return Object.assign({}, state, {
count: state.count - 1
})
}
},
})
const store = createStore(preducers.reducer);
store.subscribe(function(){
console.log(`Count is ${store.getState().count}`)
})
store.dispatch({ type: "INCREASE" }) //=> Count is 1
store.dispatch({ type: "INCREASE" }) //=> Count is 2
store.dispatch({ type: "DECREASE" }) //=> Count is 1
Named Actions is a handy tool designed to avoid action types sharing over your app and keep them only as a system-level names. The only place where you can see action types is the reducer declaration file.
To setup Named Actions Processor provides a wrap(store)
function
const store = createStore(preducers.reducer);
const actions = preducers.wrap(store);
store.subscribe(function(){
console.log(`Count is ${store.getState().count}`)
})
actions.increase() //=> Count is 1
actions.increase() //=> Count is 2
actions.decrease() //=> Count is 1
When looking at these examples you maybe wonder how to provide data to reducer when you need to perform more complex mutations to the state. It's also very simple:
const initialState = {
name: "John",
surname: "Sena"
};
const preducers = processor(initialState, {
setName: {
type: "SET_NAME",
action(name){
return { name: name }
},
process(state, payload){
return Object.assign({}, state, {
name: payload.name
})
}
},
setSurname: {
type: "SET_SURNAME",
action(surname){
return surname
},
process(state, surname){
return {...state, surname}
}
},
})
const store = createStore(preducers.reducer);
store.subscribe(function(){
const {name, surname} = store.getState();
console.log(`Hello ${name} ${surname}`)
})
store.setName("Mike") //=> Hello Mike Sena
store.setSurname("Wasowski") //=> Hello Mike Wasowski
As you can see, action creators works just like you used to. The only difference is that reducer function takes only a payload part of an action, everything else Processor does under the hood, so you don't need to deal with action types at all.
If your actions are so simple, so they don't need any additional data then you can get rid of action creator at all, Processor will create Named Action for you automatically.
Also if you need to pass some data to an action "as-is", then you can pass any number of arguments to Named Actions. The only crucial difference here is that if you're passing more than one argument to a Named Action, then you will get an array inside of reducer. To clarify this part look at the example below:
const initialState = {
name: "John",
surname: "Sena"
};
const preducers = processor(initialState, {
setFullName: {
type: "SET_NAME",
process(state, payload){
const [name, surname] = payload
return Object.assign({}, state, { name, surname })
}
}
})
const store = createStore(preducers.reducer);
store.subscribe(function(){
const {name, surname} = store.getState();
console.log(`Hello ${name} ${surname}`)
})
store.setFullName("Mike", "Shinoda") //=> Hello Mike Shinoda
You can notice that we use array destructuring syntax to create variables with name
and surname
. That's because we didn't create any action creator, so Processor has created it for us. As Processor doesn't know anything about data that we're passing and how to deal with it, it just transferring all arguments that we're passing to Named Action down to the reducer function. It causes the payload to be an array.
Otherwise, is we're passing only one argument to a Named Action, then we'll get a single value:
const initialState = {
filter: "ALL",
};
const preducers = processor(initialState, {
setFilter: {
type: "SET_FILTER",
process(state, filter){
return Object.assign({}, state, { filter })
}
}
})
const store = createStore(preducers.reducer);
store.subscribe(function(){
console.log(`Filter: ${store.getState().filter}`)
})
store.setFilter("COMPLETED") //=> Filter: COMPLETED
You're might be familiar with redux's combineReducers
function. Processor has its own alternative with similar functionality. Let's say you have two scopes in your state: user
and todos
. To separate reducers for these scopes you can use multi(setup[, initialState])
function.
import { createStore } from 'redux';
import { multi as processor } from 'redux-processor';
const initialState = {
user: {
name: "Mike",
surname: "Shinoda"
},
todos: []
};
const mpreducers = processor({
user: {
setName: {
type: "SET_NAME",
process (state, name) => { ...state, name }
}
setSurname: {
type: "SET_SURNAME",
process (state, surname) => { ...state, surname }
}
},
todos: {
add: {
type: "ADD_TODO",
process(state, payload){
const todo = Object.assign({
id: generateTodoID()
}, payload)
return [...state, todo]
}
}
}
}, initialState)
const store = createStore(mpreducers.reducer);
const actions = mpreducers.wrap(store);
actions.user.setName("Nicholas");
actions.user.setSurname("Cage");
actions.todos.add("First todo");
actions.todos.add("Second todo");
If you want to define initial state and structure for each reducer individually, then take a look at next example which works just like the previous one, but each reducer takes care of its part of a state. Resulting state is a combination of states returned by individual state chunks:
import { createStore } from 'redux';
import { multi as processor } from 'redux-processor';
const mpreducers = processor({
user: {
state: {
name: "Mike",
surname: "Shinoda"
},
setName: {
type: "SET_NAME",
process (state, name) => { ...state, name }
}
setSurname: {
type: "SET_SURNAME",
process (state, surname) => { ...state, surname }
}
},
todos: {
state: [],
add: {
type: "ADD_TODO",
process(state, payload){
const todo = Object.assign({
id: generateTodoID()
}, payload)
return [...state, todo]
}
}
}
})
const store = createStore(mpreducers.reducer);
const actions = mpreducers.wrap(store);
actions.user.setName("Nicholas");
actions.user.setSurname("Cage");
actions.todos.add("First todo");
actions.todos.add("Second todo");
In this case you don't need to provide initial state for processor
, instead you providing state for each state chunk itself.
Sometimes it might be useful to be able to reset whole redux state completely. For trat purpose reduxp-processor
offers resetStore()
method that does exactly what it says. Notice that unlike regular reducers, resetStore()
lives at the root of actions
provided by wrap()
method.
const store = createStore(mpreducers.reducer);
const actions = mpreducers.wrap(store);
actions.user.setName("Nicholas");
actions.user.setSurname("Cage");
actions.resetStore()
With processor it's very simple to organize your code using separate files for each of your state chunks:
user_reducer.js
module.exports.state = {
name: "Mike",
surname: "Shinoda"
}
module.exports.setName = {
type: "SET_NAME",
process (state, name) => { ...state, name }
}
module.exports.setSurname = {
type: "SET_SURNAME",
process (state, surname) => { ...state, surname }
}
todos_reducer.js
module.exports.state = []
module.exports.add = {
type: "ADD_TODO",
process(state, payload){
const todo = Object.assign({
id: generateTodoID()
}, payload)
return [...state, todo]
}
}
app.js
import { createStore } from 'redux';
import { multi as processor } from 'redux-processor';
const mpreducers = processor({
user: require('reducers/user_reducer.js'),
todos: require('reducers/todos_reducer.js')
})
const store = createStore(mpreducers.reducer);
const actions = mpreducers.wrap(store);
actions.user.setName("Nicholas");
actions.user.setSurname("Cage");
actions.todos.add("First todo");
actions.todos.add("Second todo");
Feel free to fork and create PRs.
MIT