Domains define the rules in which resolved actions are converted into
new state. They are added to a Microcosm instance using addDomain
:
// Mount a domain that operates on a specific key in repo state.
// Any operations the domain handles are recorded at `repo.state.key`:
repo.addDomain('key', Domain)
// Mount a domain that operates on all repo state, handlers will
// write directly to `repo.state` itself:
repo.addDomain(Domain)
Domains do not enforce any particular structure. However specific methods can be defined on domains to configure behavior at key points in a Microcosm's lifecycle.
There are two ways to create a domains: as a class, and as a plain object. The
usage is roughly the same for both versions, the class form can additionally
take advantage of having a constructor
.
class Domain {
setup(repo, options) {
// Run startup behavior
}
teardown(repo) {
// Clean up any setup behavior
}
handleAction(state, payload) {
// Old state in, new state out...
let newState = { ...state, prop: payload.prop }
return newState
}
register() {
return {
[action]: this.handleAction
}
}
}
repo.addDomain('key', Domain)
const Domain = {
setup(repo, options) {
// Run startup behavior
},
teardown(repo) {
// Clean up any setup behavior
},
handleAction(state, payload) {
let newState = { ...state, prop: payload.prop }
return newState
},
register() {
return {
[action]: this.handleAction
}
}
}
repo.addDomain('key', Domain)
Microcosm calls Object.create
on the simple object form, preventing any
assignments within the Domain from polluting other instances. In this way, they
are somewhat similar to the class form.
Domains can provide a register
method to dictate what actions they
listen to:
const Domain = {
// ... Other domain methods
register() {
return {
[action]: {
open: this.setLoading,
loading: this.setProgress,
done: this.addRecord,
error: this.setError,
cancelled: this.setCancelled
}
}
}
}
action
referenced directly, like [action]: callback
, refer to the
done
state.
Generate the starting value for the particular state this domain is managing. This will be called by the Microcosm using this domain when it is started.
var Planets = {
getInitialState() {
return []
}
}
Setup runs right after a domain is added to a Microcosm, but before it runs getInitialState. This is useful for one-time setup instructions.
Options are passed from the second argument of
repo.addDomain
. Additionally, options.key
indicates the key where
the domain was mounted:
let repo = new Microcosm()
class Planets {
setup(repo, options) {
console.log(options.key) // "planets"
}
}
repo.addDomain('planets', Planets)
Runs whenever a Microcosm is torn down. This usually happens when a
Presenter component unmounts. Useful for cleaning up work done in
setup()
.
Allows a domain to transform data before it leaves the system. It gives the domain the opportunity to reduce non-primitive values into JSON.
For example, if using ImmutableJS, this might look like:
const Planets = {
getInitialState() {
return Immutable.List()
},
serialize(planets) {
return planets.toJSON()
}
}
Allows data to be transformed into a valid shape before it enters a
Microcosm. This is the reverse operation to serialize
. Drawing from
the example in serialize
:
const Planets = {
getInitialState() {
return Immutable.List()
},
serialize(planets) {
return planets.toJSON()
},
deserialize(raw) {
return Immutable.List(raw)
}
}
Returns an object mapping actions to methods on the domain. This is the communication point between a domain and the rest of the system.
import { addPlanet } from '../actions/planets'
const Planets = {
//...
register() {
return {
[addPlanet]: this.append
}
},
append(planets, params) {
return planets.concat(params)
}
}
repo.push(Actions.add, { name: 'earth' }) // this will add Earth
You can assign multiple handlers to an action by passing an array:
import { addPlanet } from '../actions/planets'
import { sortBy } from 'lodash'
const Planets = {
//...
register() {
return {
[addPlanet]: [this.append, this.sort]
}
},
append(planets, params) {
return planets.concat(params)
},
sort(planets) {
return sortBy(planets, 'name')
}
}
repo.push(Actions.add, { name: 'earth' }) // this will add Earth
These handlers are processed from left to right, receiving the result of the prior handler as the first argument.
Specifies default options a Domain is instantiated with. This provides a concise way to configure sensible defaults for setup options:
class Counter {
static defaults = {
start: 0
}
setup(repo, { start }) {
console.log(start) // 0
}
}
let repo = new Microcosm()
repo.addDomain('counter', Counter) // default start is 0
When instantiated, default options are determined in the following order:
- Microcosm defaults
- Microcosm instantiation options
- Domain defaults
- Options passed to
repo.addDomain
.