Counter Example

In this guide, we’ll walk through the process of creating a simple Counter app.

counter

Source Code

You can get source code for counter app from here

git clone https://github.com/almin/almin.git

cd almin/examples/counter
npm install
npm start
# manually open
open http://localhost:8080/

The purpose of counter

  1. Press button and count up!

End.

:memo: Notes: Recommendation

1 UseCase = 1 file

UseCase

We start implementing the UseCase.

  1. Press button and count up!

Start to create IncrementalCounterUseCase class.

"use strict";
import {UseCase} from "almin"
export default class IncrementalCounterUseCase extends UseCase {
    // UseCase should implement #execute method
    execute() {
        // Write the UseCase code
    }
}

We want to update counter app state, if the IncrementalCounterUseCase is executed.

Simply, put counter app state to a Store.

Store

Second, We create CounterStore class.

"use strict";
import {Store} from "almin";
export class CounterStore extends Store {
    constructor() {
        super();
        // receive event from UseCase, then update state
    }

    // return state object
    getState() {
        return {
            count: 0 
        }
    }
}

Almin's Store can receive the dispatched event from a UseCase.

:thought_balloon: Image:

  1. IncrementalCounterUseCase dispatch "increment" event.
  2. CounterStore receive the dispatched "increment" event and update own state.

This pattern is the same Flux architecture.

flux-diagram-white-background

In flux:

  1. dispatch "increment" action via ActionCreator
  2. Store receive "increment" action and update own state

UseCase dispatch -> Store

Return to IncrementalCounterUseCase and add "dispatch increment event"

// LICENSE : MIT
"use strict";
import { UseCase } from "almin";
export default class IncrementalCounterUseCase extends UseCase {
    // IncrementalCounterUseCase dispatch "increment" ----> Store
    // UseCase should implement #execute method
    execute() {
        this.dispatch({
            type: "increment"
        });
    }
}

A class inherited UseCase has this.dispatch(payload); method.

payload object must have type property.

{
    "type": "type"
}

is a minimal payload object.

Of course, you can include other property to the payload.

{
    "type": "show",
    "value": "value"
}

So, IncrementalCounterUseCase dispatch "increment" payload.

UseCase -> Store received

Next, We want to add the feature that can received "increment" payload to CounterStore.

A class inherited Store can implement receivePayload method.

"use strict";
import { Store } from "almin";
export class CounterStore extends Store {
    constructor() {
        super();
        // initial state
        this.state = {
            count: 0
        };
    }

    // receive event from UseCase, then update state
    receivePayload(payload) {
        if(payload.type === "increment"){
            this.state.count++;
        }
    }

    // return the state
    getState() {
        return this.state;
    }
}

All that is updating CounterStore's state!

But, We can separate the state and CounterStore as files. It means that we can create CounterState.

Store

  • Observe dispatch events and update state
    • Write state: receivePayload()
    • Read state: getState()

State

  • It is state!

State

We have created CounterState.js.

CounterStates main purpose

  • receive "payload" and return state.
// LICENSE : MIT
"use strict";
// reduce function
export default class CounterState {
    /**
     * @param {Number} count
     */
    constructor({ count }) {
        this.count = count;
    }

    reduce(payload) {
        switch (payload.type) {
            // Increment Counter
            case "increment":
                return new CounterState({
                    count: this.count + 1
                });
            default:
                return this;
        }
    }
}

You may have seen the pattern. So, It is reducer in the Redux.

Store -> State: NewState

Finally, we have added some code to CounterStore

  1. Receive dispatched event, then update CounterState
  2. CounterStore#getState return the instance of CounterState

A class inherited Store has this.setState() method that update own state if needed.

// LICENSE : MIT
"use strict";
import { Store } from "almin";
import CounterState from "./CounterState";
export class CounterStore extends Store {
    constructor() {
        super();
        // initial state
        this.state = new CounterState({
            count: 0
        });
    }

    // receive event from UseCase, then update state
    receivePayload(payload) {
        this.setState(this.state.reduce(payload));
    }

    // return own state
    getState() {
        return this.state;
    }
}

:memo: Note: Testing

We can test above classes independently.

View Integration

This example use React.

App

We will create App.js is the root of component aka. Container component.

And, create Context object that is communicator between Store and UseCase.

import {Context, Dispatcher} from "almin";
import {CounterStore} from "../store/CounterStore";
// a single dispatcher
const dispatcher = new Dispatcher();
// initialize store
const counterStore = new CounterStore();
// create store group
const storeGroup = new StoreGroup({
    // stateName : store
    "counter": counterStore
});
// create context
const appContext = new Context({
    dispatcher,
    store: storeGroup
});

Full code of App.js:

// LICENSE : MIT
"use strict";
import React from "react";
import { Context, Dispatcher, StoreGroup } from "almin";
import { CounterStore } from "../store/CounterStore";
// a single dispatcher
const dispatcher = new Dispatcher();
// a single store
const counterStore = new CounterStore();
// create store group
const storeGroup = new StoreGroup({
    // stateName : store
    "counter": counterStore
});
// create context
const appContext = new Context({
    dispatcher,
    store: storeGroup
});
import Counter from "./Counter";
export default class App extends React.Component {
    constructor(...args) {
        super(...args);
        this.state = appContext.getState();
    }

    componentDidMount() {
        // when change store, update component
        const onChangeHandler = () => {
            this.setState(appContext.getState());
        };
        appContext.onChange(onChangeHandler);
    }

    render() {
        /**
         * Where is "CounterState" come from?
         * It is `key` of counterStore in StoreGroup.
         *
         *
     * const storeGroup = new StoreGroup({
     *   "counter": counterStore
     * });
     * ```
     * @type {CounterState}
     */
    const counterState = this.state.counter;
    return <Counter counterState={counterState}
                    appContext={appContext}/>;
}

}


Focus on `onChange`:

```js
appContext.onChange(onChangeHandler);

If CounterStore's state is changed(or emitChange()ed), call onChangeHandler. onChangeHandler do update App component's state.

Counter component

Counter component receive counterState and appContext via this.props..

CounterComponent.propTypes = {
    appContext: React.PropTypes.instanceOf(Context).isRequired,
    counterState: React.PropTypes.instanceOf(CounterState).isRequired
};

End

We have created simple counter app.

Writing the pattern in this guide is the same of Flux pattern.

almin-flux.png

Next: We learn domain model and CQRS pattern while creating TodoMVC app.

results matching ""

    No results matching ""