Relessjs: state management without Redux’ overhead

Albert G
4 min readSep 25, 2017

--

Haven’t you been bothered by the sheer amount of work you’d have to put into a project when you use Redux as your state management tool?

Want to use Relessjs immediately? Go to npmjs.com/package/relessjs

Well I have! Especially when the project grows. Then I want to keep things DRY and put my Action Types in one place, because I need to reference them everywhere. Yes, I know, you can still put strings { type: “FILTER" } in there, but then I’ve got to match that to my reducers so that’s already wet (not-DRY).

It’s especially annoying when I’d have to create action factories to do some async stuff. Calling the factory, referencing the action-types, using the type’s in the created actions and, don’t forget it, reducing them.

Implementing search in Redux

Let’s say I have a ToDo app and I want todo’s to be filtered based on text. A simple search feature. To implement this I’d have to do the following:

  1. add the action type var actions = { filter: “FILTER” } to todo-actions.js (this is where I put all my constants, to make sure I keep it DRY)
  2. import todo-actions.js into the todo-reducers.js
  3. create a new case for actions.filter in the reducer
  4. import todo-actions.js into search-component.js
  5. implement the dispatch({ type: actions.filter, payload: event.target.value }) in order to get the loop going

Yes, too… much… work… We can do better:

  • remove step 1 (no need for an action type);
  • remove step 2 (no action-types means: no constants);
  • remove step 4 (idem); and
  • instead of step 5 (calling dispatch()), we would just call the reducerreducers.search(payload) and
  • implement step 3 in the reducer

Implementing search in Reless

Relessjs: Redux without Actions or Dispatcher (made with Asciiflow and Shaky)

I’ve been building Relessjs. A package to provide the same core concepts of the Flux architecture as Redux does but with less overhead.

So now we just call a Reducer from the View and we get a new State on which the View will be rerendered.

Relessjs: unidirectional dataflow without overhead

Now my new search feature for the ToDo app is simplified to only 2 steps:

  1. Create the reducer named search(payload) to the list of reducers from Reless
  2. Call the reducers.search(input.value) from the new search component

Awesome right! Just create and call.

Simplicity: create a reducer, call a reducer

The power of Reless

Below are a few examples of the power that Reless has to offer.

Setting a value, to simply set the state, the reducer is of the following type payload => state: a function that gets a payload and returns a new partial state. Partial state, because it’s being merged with the existing state. This removes the need for an Object.assign({}, value) or { ...state } on the root level.

Incrementing a counter, updating state based on another state:

As shown above (setting a value), you don’t get the state as a second parameter in the first function. To use the state Reless allows you to return a function from the reducer of type state => state. This way you can use the state passed in the second function.

var store = new Reless({
state: { counter: 0 },
reducers: {
increment: payload => state => ({ counter: state.counter + 1 })
}
})

Fetching data, doing some async stuff (and using partial state):

Well, now you know the drill. You can return another function of type reducers => state where you can call another reducer in the same manner as mentioned before and/or return a state that goes effective immediately after finishing the function.

var store = new Reless({
state: { todos: [] },
reducers: {
getAllTodos: payload => state => reducers => {
fetch('some.url/api/todos')
.then((todos) => reducers.setTodos(todos))
return ({ loading: true })
},
// since Reless does a merge we can return
// a partial state instead of a full state
setTodos: todos => ({ todos })
})

Counting down a timer, doing something continuously based on the latest state:

For this we need to do a little trick. Instead of calling a reducer with a payload we call it with another function that returns a payload state => payload. This way we get access to the latest state.

(In the particular case below, you can also just call decrement, but in other cases you might want to call the reducer (setCounter) with a payload)

var store = new Reless({
state: { counter: 20 },
reducers: {
decrement: () => state => ({ state.counter: state.counter-1 }),
setCounter: payload => ({ counter: payload }),
getAllTodos: payload => state => reducers => {
var countdownHandle = setInterval(() => {
if (state.counter)
reducers.setCounter(state => state.counter - 1)
else
clearInterval(countdownHandle)
}, 1000)
}
})

Don’t forget to clap if you liked this. However, if you think something is badly worded, please give me some feedback. I do appreciate it.

--

--