In this guide, we’ll walk through the process of creating a simple Counter app.
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/
- Press button and count up!
End.
1 UseCase = 1 file
We start implementing the UseCase.
- 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.
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.
💭 Image:
- IncrementalCounterUseCase dispatch "increment" event.
- CounterStore receive the dispatched "increment" event and update own state.
This pattern is the same Flux architecture.
In flux:
- dispatch "increment" action via ActionCreator
- Store receive "increment" action and update own state
Return to IncrementalCounterUseCase
and add "dispatch increment event"
include, IncrementalCounterUseCase.js
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.
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()
- Write state:
State
- It is state!
We have created CounterState.js
.
CounterState
s main purpose
- receive "payload" and return state.
You may have seen the pattern. So, It is reducer in the Redux.
Finally, we have added some code to CounterStore
- Receive dispatched event, then update
CounterState
CounterStore#getState
return the instance ofCounterState
A class inherited Store
has this.setState()
method that update own state if needed.
We can test above classes independently.
This example use React.
We will create index.js
is the root of the application.
First, we 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
});
Second, We will pass the appContext
to App
component and render to DOM.
ReactDOM.render(<App appContext={appContext} />, document.getElementById("js-app"))
Full code of index.js
:
We will create App.js
is the root of component aka. Container component.
It receive appContext
from index.js
and use it.
Full code of App.js
:
Root Component has state that sync to almin's state.
Focus on onChange
:
// update component's state with store's state when store is changed
const onChangeHandler = () => {
this.setState(appContext.getState());
};
appContext.onChange(onChangeHandler);
If CounterStore
's state is changed(or emitChange()
ed), call onChangeHandler
.
onChangeHandler
do update App
component's state.
Counter component receive counterState
and appContext
via this.props.
.
CounterComponent.propTypes = {
appContext: React.PropTypes.instanceOf(Context).isRequired,
counterState: React.PropTypes.instanceOf(CounterState).isRequired
};
We can execute IncrementalCounterUseCase
when Counter's Increment button is clicked.
incrementCounter() {
// execute IncrementalCounterUseCase with new count value
const context = this.props.appContext;
context.useCase(new IncrementalCounterUseCase()).execute();
}
Execute IncrementalCounterUseCase
and work following:
- Execute
IncrementalCounterUseCase
CounterStore
is updated(create newCounterState
)App
Component's state is updated viaonChangeHandler
Counter
receive newCounterState
, refresh view
Full code of Counter.js
:
We have created simple counter app.
Writing the pattern in this guide is the same of Flux pattern.
Next: We learn domain model and CQRS pattern while creating TodoMVC app.