State Management without Reducers
Tags: javascript, webdev, productivity, typescript
Published: 2020-11-11 20:33:22

If you're a developer like me who has problems connecting the disconnected abstract pieces of code and have a hard time understanding how and where the state is being updated. Then you might also be hitting the limits of the human brain's limited [working memory](https://en.wikipedia.org/wiki/Working_memory).

I think I have found a solution, a way to get rid of reducers without losing any benefits. During the last 10 months, I built an efficient state manager called [ActiveJS](https://activejs.dev) that doesn't use reducers.

> Instead of dispatching actions and let reducers implicitly work their magic, we just dispatch reducer like pure-functions and get rid of actions in the process; two birds with one stone.

These pure-functions are called **producers** because they take the current state and produce a new state.

So reducers reduce, and producers produce, but essentially they are doing the same thing, updating the state. Sounds good, right?

Let's put the theory to test, and write some code:

## State Management with Reducers
First, let's look at the good old reducers.
This is how a simple counter looks like, implemented with the champion of reducers, Redux.

```ts
const counter = (state, action) => {
  if (typeof state === 'undefined') {
    return 0
  }

  // these are our reducers
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// initialize the store and bind the reducers
const store = Redux.createStore(counter)

// subscribe for the state changes and log it to console
store.subscribe(() => console.log(store.getState()));
// logs 0 immediately and will log any future values

// dispatch the action for increment
store.dispatch({ type: 'INCREMENT' }) // makes the count 1

// dispatch the action for decrement
store.dispatch({ type: 'DECREMENT' }) // makes the count 0
```

Now let's see what happens when we replace reducers with producers.

## State Management with Producers
For this, we'd use ActiveJS, the new kid on the block, it's got built-in reactive data structures called [Units](https://docs.activejs.dev/fundamentals/units), that store and provide native data structures as value, like `number`, `string`, `array`, etc.

One of those Units is [NumUnit](https://docs.activejs.dev/fundamentals/units/numunit), it stores a `number` value and ensures that it stays a `number`, even `NaN` isn't allowed.

 We'd use the NumUnit to implement our counter because we expect the **count** to always be a `number`.

```ts
// initialize a reactive data structure to store numbers
const counter = new NumUnit() // with default initial-value 0

// two producers, pure-functions to produce an appropriate new value
const increment = value => value + 1 
const decrement = value => value - 1

// subscribe for reactive value access, and log the value
counter.subscribe(value => console.log(value))
// immediately logs 0, and will log any future values

// dispatch the "increment" producer for increment
counter.dispatch(increment); // you'll see 1 in the console
// the pure function is called with the current value and
// the returned value is dispatched automatically

// dispatch the "decrement" producer for decrement
counter.dispatch(decrement); // you'll see 0 in the console
```

> Look ma, No Reducers!

Easy right?

### What about actions with payload?
Let's say we want to increment the current value after multiplying it with the provided number as payload, let's see how that producer would look like.
```ts
const multiplyAndIncrement = multiplier => {
  // the wrapper function acts as an action, and
  // this is our producer now
  return value => value * multiplier + 1
}

// assume the current value of the counter is 2, for easy demonstration

// dispatch the producer
counter.dispatch(multiplyAndIncrement(3))
// the new value becomes 7, because 2 * 3 + 1 is 7

// we call multiplyAndIncrement function and it returns the producer-function
// the dispatch method calls the returned producer with the current value
// the returned value from the producer is used as new value
```

The theory checks out, the code is still functional, easily testable, and the flow of the code is not broken.

But producers are just one of the good things about Units, there are so many features baked in.

Here are some of those features that are relevant for our "counter":

### Direct Dispatch
If you don't have a need to use producers, you can just dispatch the new value directly.
```ts
counter.dispatch(2) // works
counter.dispatch(3) // works
```

### Direct Value Access
If you are not doing something reactively, and just need the value, you can access it directly.
```ts
// static value access
counter.value() // returns the current value immediately
```

### Preventing Invalid Data Types
For this we don't have to do anything, NumUnit takes care of that.
It only takes in a `number`, which ensures that it always provides a `number` value. Saves you the need for any sanity checks.
```ts
counter.dispatch('an evil string') // won't work
counter.dispatch({nein: 'nein nein'}) // won't work
counter.dispatch(NaN) // won't work
counter.dispatch(() => NaN) // won't work
counter.dispatch(['what', 'about', 'this']) // won't work
```

### Preventing Duplicate Dispatch
Preventing duplicate values is as easy as providing a configuration option.
```ts
const counter = new NumUnit({distinctDispatch: true})
// that's it, done

counter.dispatch(2) // works
counter.dispatch(2) // won't work, it'll get ignored

counter.dispatch(3) // works
counter.dispatch(3) // won't work, it'll get ignored
```

### Preventing Negative Values
It makes sense that our counter should only have positive values. And making sure of that is also easy by providing a function, it'll be called by the Unit to check if the new value should be updated or not.
```ts
const counter = new NumUnit({
 // If it returns a falsy value for a dispatch, that dispatch is ignored.
 // So just return true if the newly dispatched value is positive.
  customDistinctDispatch: (prev, newValue) => newValue >= 0
})
// that's it, done

counter.dispatch(2) // works
counter.dispatch(-2) // won't work, it'll get ignored

counter.dispatch(3) // works
counter.dispatch(-3) // won't work, it'll get ignored
```

That's it, folks, for now.

Here's the [StackBlitz playground link](https://stackblitz.com/edit/rxjs-daesrs?file=index.ts) if you want to try it out yourself.

There are even more things that NumUnit alone and Units at large can do. Here's an article demonstrating some of those features.
{% link https://dev.to/dabalyan/state-management-with-a-single-line-of-code-2llg %}

Cheers

[🌏 ActiveJS Website](https://activejs.dev)
[📖 ActiveJS Documentation](https://docs.activejs.dev)
[🤾‍♂️ ActiveJS Playground](https://activejs.dev/#/playground/numunit)
[💻 ActiveJS GitHub Repo](https://github.com/activejs/activejs) (drop a ⭐ maybe :)