Reduxsauce is a library that provides concise methods for writing action creators and reducers. These methods allow action types to be mapped explicitly to reducers, and this removes the need to implement a switch-case statement. The reducer logic can then be defined separately from this mapping. As a result, the consolidated redux code becomes highly readable, and much easier to maintain and build upon. Additionally, each reducer function can be exported and thus more easily tested. In the end, all the benefits of the “ducks” design pattern can be maintained, while the lines of code required can be significantly reduced.
What is it? Redux!
“A predictable state container for JavaScript apps”
In other words
Redux is a library to keep data organized and consistent across an app
Illustrative Story Time!
Depositing $$$ 1. "I would like to deposit my wads of cash, please" 2. "Great, I can definitely help you!" 3. "Balance: 1 Billion Dollars" => We make a request to the teller => The teller accepts our cash, counts it, loads it, etc => Our bank account reflects any new changes
$$$ in the bank = Data in the app
3 Main Components of Redux
Action – The request made to the teller
Reducer – The teller who interprets our request & follows the proper procedure
Store – Bank vault where all money is stored
Things to note:
- A law-abiding customer cannot go straight to the vault and take money => Data in the Store cannot be directly changed
- A customer must make a request that the teller understands => Only specific actions can be properly interpreted
- In this story, there is only one single bank in the entire world => There is only one store in an app
Redux manages app state, but…
what is State???.
A Plain-Old-Javascript-Object (POJO) that holds our app’s data
Our story: { balance: 1000000000 }
Slightly more realistic:
{ user: { email: "pj@smartlogic.io", username: "thepeej", groupIds: [1, 5, 48] }, firstTimeLoaded: false, }
Action
The customer’s request
– A POJO that expresses user’s intent
– Has a required attribute of “type”
{ type: "DEPOSIT_MONEY", payload: 1000000000 }
{ type: "WITHDRAW_TWENTY" }
Reducer
The bank teller
- A pure JS function
- Accepts two parameters – State & Action
- Sets initial state
- Always returns a new State object
function bankTransactions(state = { balance: 100 }, action) {
switch (action.type) {
case 'DEPOSIT_MONEY' :
return { ... state, balance: state.balance + action.payload}
case 'WITHDRAW_TWENTY' :
return {... state, balance: state.balance - 20}
default :
return state
}
}
****
Problem! – A reducer only returns a new state!
let state
function bankTransactions(state = {balance: 100}, action) {
switch (action.type) {
case 'DEPOSIT_MONEY' :
return {...state, balance: state.balance + action.payload}
case 'WITHDRAW_TWENTY' :
return {... state, balance: state.balance - 20}
default :
return state
}
}
bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }
bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }
bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }
Store
The Bank Vault
Ties everything together
- Declares and encapsulates application state
- getState(): Provides access to application state
- dispatch(): “Saves” state changes from action/reducer output
- There should be only one Store in an application
Dispatch
The only way to save state changes
Problem:
let state
bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }
bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }
bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }
Solution:
function dispatch(action) {
state = bankTransactions(state, action)
}
createStore
Requires a reducer to be passed in
function createStore(reducer) {
let state
function dispatch(action) {
state = reducer(state, action)
}
function getState() {
return state
}
dispatch({})
return {
dispatch,
getState
}
}
( subscribe() & listeners are omitted in example )
createStore
How do we use it?
let store = createStore(bankTransactions)
store.getState()
// => { balance: 100 }
store.dispatch({type: 'DEPOSIT_CASH', payload: 40})
store.getState()
// => { balance: 120 }
store.dispatch({type: 'WITHDRAW_TWENTY'})
store.getState()
// => { balance: 80 }
Reduxsauce
Why use it?
– Keeps Redux code well organized
– Makes code easier to read and maintain
– Allows for cleaner code expandability
– Provides easier testing of Reducers
By what means?
– createReducer()
– createActions()
createReducer
import { createReducer } from 'reduxsauce'
// the initial state of this reducer
const INITIAL_STATE = { balance: 100 }
const depositCash = (state, action) => {
return {... state, balance: state.balance + action.payload}
}
const withdrawTwenty = (state, action) => {
return {... state, balance: state.balance - 20}
}
// map our action types to our reducer functions
const HANDLERS = {
['DEPOSIT_CASH']: depositCash,
['WITHDRAW_TWENTY']: withdrawTwenty
}
export default createReducer(INITIAL_STATE, HANDLERS)
createActions
Now, let’s talk about action creators
A function that returns (‘creates’) an action
function depositMoney(amount) {
return { type: 'DEPOSIT_MONEY', payload: amount }
}
depositMoney(50)
// => { type: "DEPOSIT_MONEY", payload: 50 }
Putting it all together
import { createReducer, createActions } from 'reduxsauce'
const { Types, Creators } = createActions({
withdrawTwenty: null,
depositCash: ['payload']
})
export default Creators
const INITIAL_STATE = { balance: 100 }
const depositCash = (state = INITIAL_STATE, action) => {
return {... state, balance: state.balance + action.payload}
}
const withdrawTwenty = (state = INITIAL_STATE, action) => {
return {... state, balance: state.balance - 20}
}
const HANDLERS = {
[Types.DEPOSIT_CASH]: depositCash,
[Types.WITHDRAW_TWENTY]: withdrawTwenty
}
export const reducer = createReducer(INITIAL_STATE, HANDLERS)
– Creators are exported for use within mapDispatchToProps()
– Reducer is exported for use within createStore()
Reference
Organizando o Redux com Duck Pattern e Redux Sauce | Diego Fernandes