onli -> one line reducer
onli-reducer is a micro-library that helps you to write applications using redux/useReducer without boilerplate.
Works with whatever state management that gives you a
dispatch
method and expects you to call such method passing an object to it with atype
property.
- onli-reducer
- No boilerplate code.
- Literally one line reducer (no need to use
switch
statement). - Direct call to actions without instructions about their
type
. - Support for asynchronous calls (dispatch/send actions from async functions).
- No need to manually write your types.
npm install --save onli-reducer
onli-reducer expose 4 methods in order to help you eliminate boilerplate code from your reducers, dispatch calls and application in general.
Although we expose 4 methods, you will normally just use the
onli
andonliSend
method.
Instead of create a reducer function with an evergrowing switch
statement inside of it, you just need to:
- Declare your actions as plain JavaScript functions;
- Attach such functions to an
actions
object; - Pass the
actions
object toonli()
helper.
The onli
helper method returns an array with 2 elements: a reducer function and an array of strings (types), that is generated based on your actions' names.
Example:
import onli from "onli-reducer"
const increment = state => state + 1
const decrement = state => state - 1
const actions = { increment, decrement }
const [countReducer, types] = onli(actions)
export { countReducer, types }
Istead of manually set the type in every dispatch call, you can just:
- Pass your
dispatch
method (from Redux or useReducer) andtypes
array (fromonli
) foronliSend
. - Call your actions directly.
Important:
All your actions will receive in the action
object dispatched at least two properties: type
and send
. You can pass any additional payload to your actions.
The type
property is a string to let your reducer know which function it has to invoke.
The send
property is an object that holds all other public actions so you can pass it to async functions in order to update your state after async calls.
You can see how it is used in a real app here.
Example:
import { onliSend } from "onli-reducer"
import { countReducer, types } from "./count.reducer"
// ...
const send = onliSend(dispatch, types)
const { increment, decrement } = send
// You can also:
// const { increment, decrement } = onliSend(dispatch, types)
// ...
<Counter
value={count}
onIncrement={() => increment()}
onDecrement={() => decrement()}
/>
See a full example with asynchronous calls so you can have a glimpse of how onli-reducer would perform in real-world applications.
In such example app you will see:
- data fetching
- loading state transition
- React hooks
useState
,useReducer
anduseContext
- React Context API
- asynchronous actions called from your reducer
- more...
See the live demo here and the source code here.
To show a comparison between the traditional usage of Redux and the new approach using onli-reducer, let's rebuild the Counter app from Redux docs.
Obs: it will be shown only the differences between the two approaches.
index.js
with Redux:
// ...
import counter from "./reducers"
const store = createStore(counter)
// ...
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({ type: "INCREMENT" })}
onDecrement={() => store.dispatch({ type: "DECREMENT" })}
/>
index.js
with Redux + onli-reducer:
// ...
import { createStore } from "redux"
import { onliSend } from "onli-reducer"
import { countReducer, types } from "./reducers"
const store = createStore(countReducer, 0)
const send = onliSend(store.dispatch, types)
const { increment, decrement } = send
// ...
<Counter
value={store.getState()}
onIncrement={() => increment()}
onDecrement={() => decrement()}
/>
reducers/index.js
with Redux:
export default (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
reducers/index.js
with Redux + onli-reducer:
import onli from "onli-reducer"
const increment = state => state + 1
const decrement = state => state - 1
const actions = { increment, decrement }
const [countReducer, types] = onli(actions)
export { countReducer, types }
You can see/edit the example above here:
OBS: although in this simple example it can not be so evident the benefits of using onli-reducer, for real-world applications the amount of boilerplate code that onli-reducer helps you to not type is considerable.
Inside your.reducer.js
file, attach your synchronous functions to the actions
object and name your asynchronous functions with an underscore, so you know they are both private and async.
These async functions will be triggered from your sync ones, and after finish their job such async functions will be able to dispatch sync functions to update the state.
It is a good practice to keep your reducer "pure", only dealing with sync functions.
You can see how it is implemented in our example app.
The onli
method expects an object that contains your public/synchrounous actions. It returns an array with two elements: a reducer (function
) and an array with strings that represents your types ([string]
).
import onli from "onli-reducer"
const increment = state => state + 1
const decrement = state => state - 1
const actions = { increment, decrement }
const [countReducer, types] = onli(actions)
export { countReducer, types }
The onliSend
method receives a dispatch
function and a types
array of strings.
It will return an object with methods attached to it. You will use these methods to dispatch actions in order to update your state.
///// Without onli-reducer
dispatch({ type: "increment" })
dispatch({ type: "increment", step: 5 })
///// With onli-reducer
increment()
increment({ step: 5 })
onliSend
also adds to your action
payload an object called send
, that holds itself, the object returned from calling onliSend(dispatch, types)
that have access to all your actions.
This is very useful because with such object you can pass it for async functions so them can dispatch actions to update your state after finish their async tasks.
// async/private action from your reducer
const _getPokemon = async ({ name, send }) => {
const { showLoading, hideLoading, updateStore } = send // <- access to your sync methods
showLoading()
try {
const { data } = await axios.get(`${URL}${name}`)
updateStore({ pokemon: data }) // <- update your state after success
hideLoading()
} catch (error) {
updateStore({ warning: "Ops... Pokémon not found" }) // <- update your state after failure
hideLoading()
}
}
Receives an actions
object and return a reducer.
Receives an actions
object and return an array with strings (types).
MIT License © Eric Douglas