command-pack
Version:
cqs pattern for redux
211 lines (158 loc) • 4.73 kB
Markdown
# Command-Pack for redux
CQS implementation for redux
https://martinfowler.com/bliki/CommandQuerySeparation.html
## Installation
```bash
npm i -S command-pack
```
## Counter Sample
### Sample for Counter
First I need to explain what is `command` and `handler`. Basically a command is just a data dictionary that carries required parameters for the `handler`.
To create a command it is needed to make a new brand class that extends command-pack `Command`.
#### Commands:
For our counter sample, we need two commands: First, IncreaseCounter to increase total and DecreaseCounter.
**- IncreaseCounter.js**
```js
import {Command} from "command-pack";
export default class IncreaseCounter extends Command {
amount;
constructor(amount) {
super();
this.amount = amount;
}
handle(state) {
return {
...state,
total: state.total + this.amount
};
}
}
```
**- DecreaseCounter.js**
```js
import {Command} from "command-pack";
export default class IncreaseCounter extends Command {
amount;
constructor(amount) {
super();
this.amount = amount;
}
handle(state) {
return {
...state,
total: state.total - this.amount
};
}
}
```
Now we have commands... and they know how to handle this data parameters with handle method by given `state`.
### Command Registration
```js
import { CommandContainer } from "command-pack";
const container = new CommandContainer("counterStore") // Store name
.setDefaultState({total: 0}) // Store default state
.register(IncreaseCounter) // our commands
.register(DecreaseCounter);
```
### CommandExecutor
```js
import { CommandContainer, CommandExecutor} from "command-pack";
CommandExecutor.add(new CommandContainer("counterStore")
.setDefaultState({total: 0})
.register(IncreaseCounter)
.register(DecreaseCounter)
);
```
### CommandExecutor.createStore() for Redux-Provider
```js
import React from 'react';
import ReactDOM from 'react-dom';
import Provider from "react-redux/src/components/Provider";
import { CommandContainer, CommandExecutor} from "command-pack";
import IncreaseCounter from "./components/counter/IncreaseCounter";
import DecreaseCounter from "./components/counter/DecreaseCounter";
import Counter from './components/counter/Counter';
CommandExecutor.add(new CommandContainer("counterStore")
.setDefaultState({total: 0})
.register(IncreaseCounter)
.register(DecreaseCounter)
);
ReactDOM
.render(
<Provider store={CommandExecutor.createStore()}>
<div>
<h2>COUNTER Sample</h2>
<Counter/>
</div>
</Provider>
, document.getElementById('root'));
registerServiceWorker();
```
### Counter React Component
```js
import React from 'react'
import {connect} from "react-redux";
import {CommandExecutor} from "command-pack";
import DecreaseCounter from "./DecreaseCounter";
import IncreaseCounter from "./IncreaseCounter";
class Counter extends React.Component {
inc() {
CommandExecutor.execute(new IncreaseCounter(1));
}
dec() {
CommandExecutor.execute(new DecreaseCounter(1));
}
render() {
const total = this.props.counterStore.total;
return (<div>Counter : {total}
<button onClick={this.dec.bind(this)}>decrease</button>
<button onClick={this.inc.bind(this)}>increase</button>
</div>)
}
}
export default connect(x => {
return {counterStore: x.counterStore}
})(Counter);
```
### ASYNC example
To solve that problem, I tried with middlewares thunk and etc... But it just increases complexity. With command-pack it is really easy to implement an async flow.
Basically you need two commands:
- `StartDownload` to start flow
- `DownloadCompleted` to get the results
**- StartDownload.js**
```js
import {Command, CommandExecutor} from "command-pack";
export default class StartDownload extends Command {
url;
constructor(url) {
super();
this.url = url;
}
handle(state) {
fetch (this.url).then (content=>{
CommandExecutor.execute (new DownloadCompleted (content));
});
return {
...state,
downloading : true
};
}
}
```
**- DownloadCompleted.js**
```js
import {Command} from "command-pack";
export default class DownloadCompleted extends Command {
content;
constructor(content) {
super();
this.content = content;
}
handle(state) {
return {
...state,
downloading : false,
content : this.content
};
}
}