🗡️
ConanJs
  • What is ConanJs?
  • Why ConanJs...
    • ... if coming from Redux
    • ... if using vanilla React
    • ... if learning React
    • ... if not using React
  • How to install/use ConanJs
  • About us / Github / Contact us
  • Demos
    • Demo Gallery
    • Conan State Demos
      • Hello World
      • Todos
        • Todos - Basic
        • Todos - Async
        • Todos - Optimistic
      • Github issues viewer
    • Conan Flow Demos
      • Authentication
  • CONAN DATA
    • General Concepts
    • Conan State
      • Actions & Reducers
        • Reducers
        • Actions
      • Creating State
      • Observing State
        • Live rendering
        • Connecting
      • Composing State
      • Scaling State
      • Orchestrating State
      • Life cycle
        • Async handling
        • Introspection
      • Testing state
    • Conan Flow
      • Creating Flows
      • Serialising Flows
      • Observing Flows
  • CONAN Runtime
  • Dependency Injection
    • General Concepts
    • Creating the Context
    • Using the Context
  • ASAPs
  • Logging
  • API
    • Main Classes
      • Conan
        • StateDef
      • ConanState
      • ConanFlow
        • UserFlowDef
        • UserStatusDef
        • Status
    • Conan State Classes
      • Thread
      • ConnectedState
      • MonitorInfo
      • MetaInfo
    • Dependency Injection
      • DiContextFactory
    • ASAPS
      • Asaps
      • Asap
Powered by GitBook
On this page
  • Introduction
  • Complex scenario
  • Initial atomic state
  • Generating the alerts
  • Streaming the alerts
  • See the code by yourself

Was this helpful?

  1. CONAN DATA
  2. Conan State

Orchestrating State

PreviousScaling StateNextLife cycle

Last updated 4 years ago

Was this helpful?

Introduction

With ConanJs you should be able to write complex interactions easily. There isn't really a single feature that allows for this, but it really is the combination of many of them.

Some of them we have already seen:

  • directly from the state

  • . All actions in ConanJs return an , so you can guarantee that you can execute logic immediately after the actions is completed.

  • . Complex use cases many times requires composing actions like, filter, map...

  • . Being able to isolate state to operate on it will also help with complex logic.

But there is a key feature that we have not explored yet, Reactions.

You can add reactions to your ConanState by calling . The principle of this very simple, you provide with some logic that will be executed every time the state changes.

Complex scenario

Initial atomic state

These example starts with four atomic states.

const stock$ = Conan.light<StockPrice[]>('stock', [{
    id: 'TSLA',
    price: 1000
},{
    id: 'AAPL',
    price: 350
}]);

const stockOrder$ = Conan.light<StockOrder[]>('stockOrders', [{
    stockId: 'AAPL',
    buy: 300,
    sell: 400
},{
    stockId: 'TSLA',
    buy: 900,
    sell: 1200
}]);

const alertsByStock$ = Conan.light<IKeyValuePairs<StockAlert[]>>('alerts', {});

const alertStream$ = alertsByStock$.map<StockAlert[]>(alertsByStock => {
    let newStream: StockAlert[] = [];
    Objects.foreachEntry(alertsByStock, (stockAlerts)=>newStream = [...newStream, ...stockAlerts])
    return newStream.sort((left, right)=>left.timestamp - right.timestamp);
});

stock$ has the code and price for each stock

stockOrder$ has the buy and sell orders that will trigger alarms

alertsByStock$ the list of alerts by stock key based on stock$ and stockOrder$

alertStream$ as we want to show a list of alerts, we use this derived state to build the list of states based on alertsByStock$ (this is explained further below)

Generating the alerts

Alerts are generated based on any change in either the stock$ or the stockOrder$, below you can see the logic for this.

stockOrder$.tuple(stock$).addDataReaction({
    name: `checking alerts`,
    dataConsumer: ([stockOrders, stocks]) => {
        let newAlerts: StockAlert[] = [];
        stockOrders.forEach(stockOrder => {
            const stock = stocks.find(it => it.id === stockOrder.stockId);
            let operation: 'buy' | 'sell' | 'keep';
            if (stock.price >= stockOrder.sell) {
                operation = 'sell';
            } else if (stock.price <= stockOrder.buy) {
                operation = 'buy';
            } else {
                operation = 'keep';
            }
            newAlerts.push({
                operation,
                orderSnapshot: stockOrder,
                stockSnapshot: stock,
                timestamp: Date.now()
            })
        });

        let nextState: IKeyValuePairs<StockAlert[]> = {...alertsByStock$.getData()};
        newAlerts.forEach(newAlert => {
            if (nextState[newAlert.stockSnapshot.id] == null) {
                nextState[newAlert.stockSnapshot.id] = [newAlert];
            } else {
                let alertsForStock: StockAlert[] = nextState[newAlert.stockSnapshot.id];
                let lastAlert = alertsForStock[alertsForStock.length - 1];
                if (
                    !Objects.deepEquals(newAlert.stockSnapshot, lastAlert.stockSnapshot) ||
                    !Objects.deepEquals(newAlert.orderSnapshot, lastAlert.orderSnapshot)
                ) {
                    alertsForStock.push(newAlert);
                }
            }
        })

        alertsByStock$.do.update(nextState);
    }
})

Merging two states with a tuple:

[...] stockOrder$.tuple(stock$) [...]

Adding a reaction:

Immediately after, we add a reaction to the tuple

[...] .addDataReaction({
    name: `checking alerts`,
    dataConsumer: ([stockOrders, stocks])=> {
    [....]
    }
})

Invoking an action:

Note that the bulk of the logic is to decide if based on the new price and order information a new alert to keep / buy or sell should be generated.

The alerts are built on the back of the reaction into:

let nextState: IKeyValuePairs<StockAlert[]>

The alerts are a map of stock code to any the list of alerts they have.

Once this object is built, all we need to do is to update the alerts state.

alertsByStock$.do.update(nextState);

Streaming the alerts

As mentioned at the beginning we ultimately want to see a stream of alerts on the screen.

Note how we easily manage to do this by composing a new state via map from alertsByStock$

const alertStream$ = alertsByStock$.map<StockAlert[]>(alertsByStock => {
    let newStream: StockAlert[] = [];
    Objects.foreachEntry(alertsByStock, (stockAlerts)=>newStream = [...newStream, ...stockAlerts])
    return newStream.sort((left, right)=>left.timestamp - right.timestamp);
});

See the code by yourself

You can see this in action here:

At the top we the stock orders, and the stock, this generate a new ConanState that will contain an array of two elements, the stock orders and the stock prices, if any of them changes, a new state will be created.

ASAP
Composition
Scoping
addDataReaction
merge with a tuple
Invoking actions
Chaining actions
https://codesandbox.io/s/github/conan-js/conan-js-examples/tree/master/orchestratingStatecodesandbox.io
Generate alerts based on buy/sell orders and the stock prices