Not all actions result in updates to application state. For example:
- Writing backup data to
localStorage
- Sending analytics events
- Persisting query information in the URL.
Effect handlers fire immediately after Domain handlers and are only called once per action state. This means that a repo's state is up to date with the latest state transitions by the time they execute.
There are two ways to create an effect: 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 Effect {
setup(repo, options) {
// Run startup behavior
}
teardown(repo) {
// Clean up any setup behavior
}
handleAction(repo, payload) {
// Respond once to an action
}
register() {
return {
[action]: this.handleAction
}
}
}
repo.addEffect(Effect)
const Effect = {
setup(repo, options) {
// Run starting behavior
},
teardown(repo) {
// Clean up
},
handleAction(repo, payload) {
// Respond once to an action
},
register() {
return {
[action]: this.handleAction
}
}
}
repo.addEffect(Effect)
Microcosm calls Object.create
on the simple object form, preventing any
assignments within the Effect from polluting other instances. In this way, they
are somewhat similar to the class form.
URL persistence is important for shareability and wayfinding. However, a full routing solution isn't always practical. On some projects we simply want to push search parameters or other meta data into a query string.
This is a perfect use case for an effect:
// /src/effects/location.js
import url from 'url'
import { patchQuery } from '../actions/query'
class Location {
updateQuery(repo) {
const { origin, hash } = window.location
const location = url.format({
host: origin,
query: repo.state.query,
hash: hash
})
window.history.pushState(null, null, location)
}
register() {
return {
[patchQuery]: this.updateQuery
}
}
}
export default Location
Setup runs right after an effect is added to a Microcosm. It receives that repo and any options passed as the second argument.
Runs whenever Microcosm::teardown
is invoked. Useful for cleaning up
work done in setup()
.
Returns an object mapping actions to methods on the effect. This is the communication point between a effect and the rest of the system.
// /src/effects/planets.js
import { addPlanet } from '../actions/planets'
class Planets {
//...
register() {
return {
[addPlanet]: this.alert
}
}
alert(repo, planet) {
alert('A planet was added! ' + planet.name)
}
}
repo.addEffect(Planets)
repo.push(addPlanet, { name: 'earth' }) // this will add Earth
You can assign multiple handlers to an action by passing an array:
// /src/effects/planets.js
import { addPlanet } from '../actions/planets'
class Planets {
//...
register () {
return {
[addPlanet.error]: [this.alert, this.trackError]
}
}
alert (repo, error) {
alert('There was an issue!)
}
trackError (repo, error) {
myLogService.trackError(error)
}
}
repo.addEffect(Planets)
repo.push(addPlanet, { name: 'earth' })
Specifies default options a Effect is instantiated with. This provides a concise way to configure sensible defaults for setup options:
class AutoSave {
static defaults = {
saveInterval: 5000
}
setup(repo, { saveInterval }) {
console.log(saveInterval) // 5000
}
}
let repo = new Microcosm()
repo.addEffect(AutoSave) // default saveInterval is 5000
When instantiated, default options are determined in the following order:
- Microcosm defaults
- Microcosm instantiation options
- Effect defaults
- Instantiation options
- Options passed to
repo.addEffect
.