S-FLUX V3, The power of Flux pattern with the ease of redux
Tags: react, javascript, flux, typescript
Published: 2020-09-22 03:30:16

**S-Flux** is a unidirectional dataflow implementation, which allows synchronizing the actions of an application to ensure visual consistency of data, and keep simplicity in their management, even if the amount of information grows over time.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i9kqkj6fpm4xdp3i104b.PNG)

The *FLux pattern* is a representation of a unidirectional data flow. This way of representing the life cycle of an information will ensure that for each cycle, all components using the data will be at the same level of information.

The *Dispatcher* will stack the *Actions*, then redistribute them to the *Stores*, which in turn will process or not the information. Once the information has been processed, each *Store* will notify the *View(s)* that more up-to-date data is available. The *View(s)* will then update their display accordingly.

An *Action* can be triggered by a *View*, a *Store*, or any other source, such as a server. In all cases, each Action will be processed sequentially by the Dispatcher.

***

S-Flux V3 provides a very streamlined approach, so you don't need to declare either the `dispatcher` or the `stores`. Only a definition of store actions is necessary, and Shadow-Flux generates on-the-fly all the helper methods, as well as all the subscription methods, with full typescript intellisense.

What you do in V3 :
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gce4e0w0ybff59ri7obc.PNG)

What it does internally :
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jf7tae86e1jnfpdqehq3.PNG)

#### Sample definition

```typescript
// State type
type CounterState = {
  counter: number;
};

// Events
const _events = Flux.withEvents({
  ForceCounter: ""
})

type TThis = TBaseStore&lt;CounterState>;

// Actions
const _actions = {
  async increase(this: TThis) {
    this.nextState({
      counter: this.getState().counter + 1
    });
  },
  async decrease(this: TThis) {
    this.nextState({
      counter: this.getState().counter - 1
    });
  },
  async setCounter(this: TThis, payload: { counter: number }) {
    this.nextState({
      counter: payload.counter
    });
    return _events.ForceCounter; // Return specific event 
  }
}

export default Flux.registerStore({
  id: "Counter",
  async init(): CounterState { 
    return { counter: 0 };
  },
  actions : _actions,
  events  : _events
});
```

#### Sample result

```typescript
import CounterStore from "path-to-store";

// CounterStore will be of type :
{
  getState() : CounterState ;
  id         : string       ;
  events: {
    All          : string ;
    ForceCounter : string ;
  },
  subscribeTo: {
    All          : ((state: CounterState) => void) => { off: () => void };
    ForceCounter : ((state: CounterState) => void) => { off: () => void };
  },
  actions: {
    increase   : () => void                             ;
    decrease   : () => void                             ;
    setCounter : (payload: { counter: number }) => void ;
  }
}
```

The CounterStore object will provide all actions methods generated from the store actions, and all subscription method for each events.

***

## Overview

In the original Flux pattern, you need to follow these step : 

* Instanciate a dispatcher
* Create one or more store(s)
* Register all stores with the dispatcher
* Instanciate a subscriber helper
* Creates Action-Helper functions to manage Action creations
*  Subscribe Store events

But now, with S-Flux v3, to get the same result you only need to :

* Declare one or more store(s)
* Use the store helper to access all available actions
* Use the store helper to subscribe all available events

## Stores

In S-Flux, stores are used to store information. A store is responsible for a precise perimeter, called scope. This perimeter will include its data model, as well as the business or technical rules it needs.

Basically, a store is a class that extends the BaseStore class. This class must describe the data model, as well as the rules applicable to this data model. To do this, the data model will be stored in a State field, and the rules described by methods that can be triggered externally by the application.

A store should only manage its own data. However, it can rely on data from another store, if necessary, during the execution of one of its rules, to fill its data.

In order to simplify the declaration, as well as store usage, S-Flux has introduced a simplification on top of the original Pattern, allowing to hide a certain complexity level, and reducing at the same time some tedious declarations necessary to retreive data. Be aware that the simplification is a helper, but underneath, the original pattern is kept and used by S-Flux.

## Actions

Actions are methods that aim to modify the state of stored data. An action will receive a payload providing the necessary information to modify the state accordingly.

The method prototype can be describe as follow :

```typescript
type TActionReturn = void | null | string | string[];

async actionName(payload?: any) => TActionReturn;
async actionName(payload?: any, For?: (...ids: string[]) => Promise<void>) => TActionReturn;
```

The payload is an object containing all the information useful for the action to update its State.

The `For` method can be used to ensure any other store will finish to process the payload before the current one will continue.Ex:

```typescript
async getTravelDetail(payload: TTravel, For: TAwaitFor) {
  await For(priceStore.id);
  this.nextState({
    ...payload,De
    price: priceStore.getState().prices[payload.travelId]
  });
}
```

The `nexstate` method is used to update the current state. By default, the state is replaced by the new value. If you want to partially update the state, you can set the flag mergeToPreviousState to true. Be aware that the update is a shallow merge, not a deep merge.

```typescript
// Since we declare the action outside the Store class, 
// we can set the this parameter to set the correct store context 
async setCounterValue(this: TBaseStore&lt;CounterState>, payload: { value: number }) {
  this.nextState(payload);
}
```

The return value will determine the event(s) that will be triggered at the end of processing. By default, for any change of state, an event of type "All" will be triggered. It is possible to trigger specific events in case of a partial change of state. The different combinations are described below.

Value	|Effect
-----|-----
`undefined`|	Global change, we notify all subscribers to any change
`null`|	No change, this will prevent any event to be triggered
`string`	|The selected event will be triggered
`string[]`|	The selected events will be triggered

The wrapper returned by the "registerStore" method will create as many Helper methods as you have defined an action. But in addition to that, the methods also take care of forward the Payload directly to the dispatcher.

```typescript
// So if you have defined this
const _actions = {
  async increase(this: TThis) {
   ...
  },
  async decrease(this: TThis) {
    ...
  },
  async setCounter(this: TThis, payload: { counter: number }) {
    ...
  }
}

// You get that in return
{
  ...
  actions: {
      increase   : () => void                             ;
      decrease   : () => void                             ;
      setCounter : (payload: { counter: number }) => void ;
    }
    ...
}
```

## Events

Events are used to notify subscribers that a change in the state of a store they subscribe to has been updated. By default, it is not necessary to create events, because a global event exists, named "All". It is propagated for any change in a store, unless otherwise specified.

For each event to which you wish to subscribe, you must register through the Dispatcher, specifying the Store ID and the name of the event. The wrapper returned by the "registerStore" method will create as many Helper methods as you have defined an event. These methods will allow a direct subscription, without the constraint of passing the store identifier and the name of the event, and this, without even using the Dispatcher.

Events are described as key/value pairs, carrying the name of the event.

```typescript
const events = {
  updated  : "updated",
  newEntry : "newEntry"
};
```

As it is a bit redundant to specify for each key its value, knowing that in most cases the value will probably be identical to the key, it is possible to use the withEvents function to simplify this syntax.

```typescript
const events = withEvents({
  updated  : "",
  newEntry : ""
});
```

Each wrapped method will be generated followign each event declared, plus the All events that will be added automatically.

```typescript
subscribeTo: {
    All      : ((state: TheStoreState) => void) => { off: () => void };
    updated  : ((state: TheStoreState) => void) => { off: () => void };
    newEntry : ((state: TheStoreState) => void) => { off: () => void };
  }
```

The returned value of a subscription is an object exposing an off method. You can call it to unsubscribe at any time.

An example with React Hook

```typescript
React.useEffect(() => {
  const sub = counterStore.subscribeTo.All(_ => setState(_));
  return () => sub.off();
}, []);
```

## Using the store helper

The usage of a helper is extremely simple. Just import the helper.

```typescript
import BlogStore from "./stores/Blog";
```
Let's assume that the Store owns the following actions :

* getLatestArticles
* getArticleDetail
* postComment

And provide the following events:

* OnGetList
* OnGetDetail
* OnPostComment

The `Helper` will automatically provide an `action` method group and a `subscription` method group.

To call the actions, use the methods in actions, and for events, the one in `subscribeTo`.

```typescript
// You can subscribe globally to any change, and update your full state
React.useEffect(() => {
  const sub = BlogStore.subscribeTo.All(state => setState(state));
  // Unsubscribe when unmount 
  return () => sub.off();
})
```

```typescript
// You can subscribe specifically to one partial event
React.useEffect(() => {
  const sub = BlogStore.subscribeTo.OnGetList(state => setLatest(state.latest));
  // Unsubscribe when unmount 
  return () => sub.off();
})
```

Now to call an action, just use the action group methods

```typescript
// You can call easily any action
function Detail() {
  return <>
    <BlogDetail 
      body      = {state.selectedBlog} 
      onComment = { 
        _ => BlogStore.actions.postComment({
          id      : _.id,
          content : _.content
        });
      }/>
  </>;
}
```
## Conclusion
I hope you liked this post.

If you want to know more, here are all the links

[Website](https://nomoredeps.github.io/shadowjs)

[Npm](https://www.npmjs.com/package/shadow-flux)

[Github](https://github.com/nomoredeps/shadow-flux)

Enjoy :smile: