Easy immutable state management with RxComp RxJs and Immer
Published: 2020-07-25 14:16:27

In this blog post we'll talk about [RxComp Store](https://github.com/actarian/rxcomp-store) a small Javascript module of [RxComp](https://github.com/actarian/rxcomp), developed with [Immer](https://github.com/immerjs/immer) as a simple alternative to [Redux](https://github.com/reduxjs/redux).

The store can however be used with any framework or VanillaJS. 
 
___
### The store
With the `useStore` factory we create the immutable store with a default value.  
The store will be consumed by a singleton service, and honoring the [single-responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle) only a specific portion of the app data will be stored.  

```js
import { useStore } from 'rxcomp-store';

const { state$ } = useStore({ todolist: [] });
```
Subscribing to the `state$` observable you always get the last immutable copy of the `state` draft.
```js
state$.subscribe(state => this.todolist = state.todolist);
```
___
### Reducing the store state
The `reducer` operator accept a reducer callback with the `observable$` payload and a mutable draft of the `state` as parameter.  
When reducer returns, an immutable copy of the state will be pushed to the `state$` observable through [Immer](https://github.com/immerjs/immer).  

```js
const { reducer } = useStore({ todolist: [] });

observable$.pipe(
  reducer((todolist, state) => state.todolist = todolist)
);
```
___
### Catching errors into the state
The `catchState` operator is used to catch the error and store it in the immutable state.  

```js
const { catchState } = useStore({ todolist: [] });

observable$.pipe(
  catchState(console.log),
);
```
You can then observe the state for errors.  

```js
state$.subscribe(state => this.error = state.error);
```
___
### Setting the busy state
The `busy$` observable will store the busy flag in the immutable state and lock future calls until the observable completes.  

```js
const { busy$ } = useStore({ todolist: [] });

busy$().pipe(
  switchMap(() => observable$),
);
```
You can then observe the busy state.  

```js
state$.subscribe(state => this.busy = state.busy);
```
___
### Loading state from Web Api Storage or Cookie
While reloading the page, you may want to reload the previous state of the app.  
First we have to initialize the store with a different `StoreType` (the default is `StoreType.Memory`) and give it a unique store name.  

```js
import { StoreType, useStore } from 'rxcomp-store';

const { cached$ } = useStore({ todolist: [] }, 
  StoreType.Session, 'todolist'
);
```
With the `cached$` observable we can retrieve the last saved state from `sessionStorage` or `localStorage` or `cookie`.  

```js
cached$((state) => state.todolist)
```
___
### All together
1. busy$ mark state as busy  
2. cached$ load data from cache  
3. reducer reduce the state to the new state  
4. catchState catch the error and reduce the state to the errored state.  

```js
import { StoreType, useStore } from 'rxcomp-store';

const { busy$, cached$, reducer, catchState } = useStore(
  { todolist: [] },
  StoreType.Session, 'todolist'
);

busy$().pipe(
  switchMap(() => 
    merge(cached$((state) => state.todolist), fromApi$).pipe(
      reducer((todolist, state) => state.todolist = todolist),
      catchState(console.log),
    )
  )
);
```
___
### Querying the store state 
The `select$` observable accept a reducer callback with an immutable copy of the `state` as parameter and returns an immutable copy of a portion of the `state` as observable.  

```js
const { select$ } = useStore({ todolist: [] });

const todolist$ = select$((state) => state.todolist);
```
___
### Querying with select
The `select` method works like the `select$` observable but doesn't return an observable.  

```js
const { select } = useStore({ todolist: [] });

const todolist = select((state) => state.todolist);
```
___
## In Conclusion  
[RxComp Store](https://github.com/actarian/rxcomp-store) works with any framework or VanillaJS,  
but is designed as a [RxComp](https://github.com/actarian/rxcomp) module.  
If you want to know more about the integration with [RxComp](https://github.com/actarian/rxcomp) you can read the introduction  article [RxComp a modern ES6 alternative to Angular component framework](https://dev.to/actarian/rxcomp-a-modern-es6-alternative-to-angular-component-framework-58nc).

If you got any idea, implementation or methods naming preferences feel free to contribute!  

![](https://rawcdn.githack.com/actarian/rxcomp-store/master/docs/img/rxcomp-store-demo.jpg?token=AAOBSISYZJXZNFFWAPGOLYC7DQKIO)  

[demo](https://actarian.github.io/rxcomp-store) - [codesandbox](https://codesandbox.io/s/rxcomp-storemodule-demo-h297m) - [repository](https://github.com/actarian/rxcomp-store)  

___
### Other methods

#### Setting the store state
The `next` method accept a reducer callback with a mutable draft of the `state` as parameter.  
When reducer returns, an immutable copy of the state will be pushed to the `state$` observable through [Immer](https://github.com/immerjs/immer).  
It works like the `reduce` operator but doesn't return an observable.  

```js
const { next } = useStore({ todolist: [] });

next((state) => state.todolist = todolist))
```
___
#### Setting the store error
The `nextError` method will store the `error` parameter in the immutable state.
It works like the `catchState` operator but is intended to use in conjunction of classic `catchError` operator.  

```js
const { nextError } = useStore({ todolist: [] });

catchError(error => nextError(error))
```