Microcosm

Changelog

Master (13.x)

12.14.0 (beta)

12.13.2

12.13.1

12.13.0

12.12.4

12.12.3

12.12.2

12.12.1

12.12.0

12.11.0

12.10.0

12.9.0

12.8.0

12.7.0

Auto-bound action status methods

Action status methods like action.resolve() and action.reject() are auto-bound. They can be passed directly into a callback without needing to wrap them in an anonymous function.

This is particularly useful when working with AJAX libraries. For example, when working with superagent:

Instead of:

import superagent from 'superagent'

function getPlanets () {
  return action => {
    let request = superagent.get('/planets')

    request.on('request', (data) => action.open(data))
    request.on('progress', (data) => action.update(data))
    request.on('abort', (data) => action.cancel(data))

    request.then((data) => action.resolve(data), (error) => action.reject(error))
  }
}

You can do:

import superagent from 'superagent'

function getPlanets () {
  return action => {
    let request = superagent.get('/planets')

    request.on('request', action.open)
    request.on('progress', action.update)
    request.on('abort', action.cancel)

    request.then(action.resolve, action.reject)
  }
}

12.6.1

12.6.0

12.5.0

Defaults

We frequently pass custom options into Microcosm to configure Domains and Effects with different options based on the environment. For example, an Effect that auto-saves user data:

class Repo extends Microcosm {
  setup ({ saveInterval }) {
    // ...
    this.addEffect(Autosave, { saveInterval })
  }
}

It can be cumbersome to chase down the default options, which may be specified as defaults in individual domains/effects. With this release, you may now define defaults using the defaults static:

class Repo extends Microcosm {
  static defaults = {
    saveInterval: 5000
  }

  setup ({ saveInterval }) {
    // ...
    this.addEffect(Autosave, { saveInterval })
  }
}

This takes advantage of the Class Fields & Static Properties Spec. This specification is still at Stage 2, however it has become common place to use this feature within React projects. If living on the edge isn’t your thing, defaults may also be configured by assigning a default property to your Microcosm subclass:

class Repo extends Microcosm {
  setup ({ saveInterval }) {
    // ...
    this.addEffect(Autosave, { saveInterval })
  }
}


Repo.defaults = {
  saveInterval: 5000
}

All Microcosm specific options, such as maxHistory will get merged into your custom defaults upon construction.

12.4.0

12.3.1

12.3.0

12.2.1

12.2.0

12.1.3

12.1.2

12.1.1

12.1.0

12.0.0

Upgrading

Microcosm

deserialize, serialize, reset, and patch only operate on keys managed by a particular Microcosm. Verify that, where you are using these methods, your application is not relying on them to inject arbitrary application state.

These methods now return the merged result of calling all the way up the hierarchy of Microcosm forks. In practice, this means that Microcosms only have to deal with the keys for domains they were assigned, which is more in line with the behavior we expect from forks.

Actions

With the exception of removing send, which was replaced with update, actions have not changed. If you have removed all deprecated action.send calls after upgrading to 11.6.0, there should be no further change required.

Domains

No more commit

Domains no longer support commit(), and subsequently shouldCommit(). We found, while useful for serializing libraries such as ImmutableJS, that it our usage of commit turned into a convenience method for always writing state in a specific way. This created an awkwardness with serializing data, and could be a source of performance problems as they continually write new object references from things like filter or slice.

So we removed it. We recommend moving this sort of behavior to getModel in the Presenter add-on.

Nested action registrations

Domains may now nest action statuses as an object:

class Domain {
  register () {
    return {
      [action]: {
        open  : this.setLoading,
        error : this.setError,
        done  : this.setDone
      }
    }
  }
}

Presenters

getModel is the new model

We frequently found ourselves wanting to access the latest model inside of our presenter. What if we wanted to fetch extra data from records pulled out of a model, or render differently if the record was missing?

Presenters now have a model property, which can be accessed after setup has completed:

class MyPresenter extends Presenter {
  getModel () {
    return { count: state => state.count }
  }

  render () {
    return (
      <ActionButton action={step} value={1}>
        {this.model.count}
      </ActionButton>
    )
  }
}

ready

setup can not have access to this.model because repo specific setup behavior might cause the model to be recalculated excessively. So we’ve added a ready method. Both ready and update have access to the last calculated model, which makes them ideal for performing some work based on it:

class MyPresenter extends Presenter {
  getModel (props) {
    return {
      user: data => data.users.find(u => u.id === props.id)
    }
  }
  ready (repo, props)
    if (this.model.user == null) {
      repo.push(this.fetchUser, props.id)
    }
  }
}

You can still do this sort of fetching inside of setup, there just won’t be a model to access. Not much of a change from 11.6.0, where this.model was not available.

render is the new view

We (Nate) got this wrong. By not using render, too much distance was created between the underlying React Component behavior and the “special treatment” received by view.

render now works just like React.Component::render, as it should be. Still, we haven’t gotten rid of view, which is useful in a couple of places, like as a getter to switch over some piece of model state:

class MyPresenter extends Presenter {
  getModel (props) {
    return {
      user: data => data.users.find(u => u.id === props.id)
    }
  }

  get view () {
    return this.model.user ? MyUserView : My404View
  }
}

view is always invoked with React.createElement. render is always called in the context of the Presenter. It is a plain-old React render method (for great justice).

intercept is the new register

Presenter::register was a confusing name for the what it did. Presenter::register allows you to catch messages sent from child view components. Catch is a reserved word, so we’ve renamed it intercept.

In the future, Presenter::register might behave more like an Effect, which is what several users have mistaken it for.

11.6.0

Upgrading

This should be a pretty simple upgrade. Just replace calls to action.send() with action.update().

11.5.1

11.5.0

11.4.0

11.3.0

11.2.2

11.2.1

11.2.0

11.1.0

11.0.0

Upgrading

We’ve successfully upgraded a few of our projects without requiring changes. However you may encounter a couple of issues.

We removed several alias, which have been deprecated during the 10.x release. These are:

Modules

Microcosm is now bundled as a single module. This reduces build size and start up times, but those using CommonJS will need to make a few changes:

// old
var Microcosm = require('microcosm')
// new
var Microcosm = require('microcosm').Microcosm

Domains

The signature for repo.addDomain must always include a key. This key can be empty. If you using this functionality, make the following change:

// old
repo.addDomain(Domain)
// new
repo.addDomain(null, Domain)

Presenter

render is now protected in the Presenter. Instead, use the view method. We believe that, in all cases, this should be as simple as renaming render to view.

Presenter now extends from PureComponent when available.

10.9.0

10.8.0

10.7.1

10.7.0

10.6.1

10.6.0

10.5.1

10.5.0

10.4.0

10.3.6

10.3.5

10.3.4

10.3.3

10.3.2

10.3.1

10.3.0

Important: this was a bad release. Please use >= 10.3.1

10.2.1

10.2.0

10.1.1

10.1.0

10.0.0

We made it! It’s been a long road, but we’re finally here.

This is a significant release. We’ve added some new tools for medium to large applications, and made some naming convention changes to make things more accurate to their purpose. Actions and Stores (now Domains) also received significant upgrades.

High level list:

No more plugins

Microcosm 9.x has a start method that must be called to begin using it. This method sets initial state and runs through all plugins, executing them in order.

We really liked plugins, however this extra step was cumbersome and makes it harder to support embedding microcosms within components. This is important for future planning, so we took advantage of the major release to remove plugins.

We’ve added a setup method to the Microcosm prototype. We’ve found most plugins can easily be converted into direct function calls, like:

class Repo extends Microcosm) {
  setup () {
    plugin(this, options)
  }
}

Domains (no longer called Stores)

State management

Repo state now has two extra phases: stage and commit. Domains handlers work against a internal staging ground. Domains can then commit state as a separate operation.

For example, when working with ImmutableJS:

const ImmutableDomain = {
  getInitialState() {
    return Immutable.Map()
  },

  shouldCommit(next, previous) {
    return Immutable.is(next, previous) === false
  },

  add(state, record) {
    return state.set(record.id, record)
  },

  remove(state, id) {
    return state.remove(id)
  }

  commit(state) {
    return Array.from(state.values())
  }
}

shouldCommit returns true by default, and commit just passes along state. When warranted, these new hooks should grant a high degree of separation between a Domain’s internal data management and the data consumed by a component tree.

Actions

Actions have been significantly upgraded to allow for complicated async operations.

We removed the generator form for actions. Instead, actions can return a function to get greater control over async operations:

function getUser (id) {
  return function (action) {
    const request = ajax('/users/' + id)

    action.open(id)

    request.on('load', (data) => action.resolve(data))

    request.on('error', (error) => action.reject(error))
  }
}

Domains can subscribe to these fine-grained action states:

const UserDomain = {
  // ... handlers
  register() {
    return {
      [getUser.open]  : this.setLoading,
      [getUser.done]  : this.updateUser,
      [getUser.error] : this.setFailure
    }
  }
}

Presenter Addon

We’ve removed the Connect and Provide addons in favor of a single Presenter addon. Though the API is different (using classes instead of higher order functions), it accomplishes the same goals.

For usage, checkout the presenter docs

Changes 10.0.0 after rc11 (released in 10.0.0)

10.0.0-rc11

10.0.0-rc10

10.0.0-rc9

10.0.0-rc8

10.0.0-rc7

10.0.0-rc6

10.0.0-rc5

10.0.0-rc4

10.0.0-rc3

10.0.0-rc2

10.0.0-rc

Upgrading

10.0.0-beta-8

10.0.0-beta7

10.0.0-beta6

Couple of bug fixes:

10.0.0-beta5

Almost there. This is an important revision. Any new changes after this should focus primarily API design (what do we call stuff?).

Microcosm

Stores

Presenter Addon

10.0.0-beta4

10.0.0-beta3

9.21.0

9.20.0

This update contains internal updates that were substantial enough to warrant a minor release. There should be no breaking changes, but actions and plugins have been improved in ways that may affect your app.

Noticeable changes

Bailing out early

A Microcosm must be started via app.start() before pushing actions. With this release, it will now throw an error when this is not the case. When upgrading, ensure that app.start() is being called before booting your application.

Promises

Before this release, the try/catch block that Promises use to identify rejections would also extend to internal Microcosm operations… and eventually React components subscribing to updates. This meant that it was possible for errors thrown by Stores and React components to be caught by Promises.

This is typically what you sign up for with Promises, however there is a very clear stopping point within Microcosm where it simply needs the value returned from a Promise. There is no additional value in continuing on with the Promise behavior, only hindrance. In this release we’ve added an escape hatch at that specific part of the lifecycle to untrap errors after that point.

This should only make working with Promises much more pleasant, and we do not anticipate it affecting the way you use Microcosm. Still, it is possible that your app does not properly handle errors from Promises. When upgrading, you should confirm this for all actions that rely on Promises.

Performance

Microcosm dispatches are roughly 1200% faster (depending on the number of stores, and event subscriptions).

Much of this is attributed to changes in the Tree data structure used to keep track of state. Specific actions, such as retrieving the root and size of the tree occur in constant time. Additionally action handlers are now memoized to prevent wasteful calls to Store register methods.

These changes have also resulted in tremendously lower memory usage.

9.19.1

9.19.0

9.18.0

9.17.0

Plugins no longer require a next argument. For example, consider:

function Plugin (app, options, next) {
  app.listen(function() {
    console.log("I changed!")
  })

  next()
}

This plugin is entirely synchronous, yet relies on next() to advance plugin installation forward. As of this release, omitting the next argument causes a plugin to be synchronously processed:

function Plugin (app, options) {
  app.listen(function() {
    console.log("I changed!")
  })
}

This is not mandatory, and designed to streamline simple plugins.

9.16.0

9.15.2

9.15.1

9.15.0

Potentially breaking changes

In a previous update, we made a change that allowed instances of microcosm to work without invoking start(). This update reverts that decision. Without intentionaly invoking start, transactional state becomes hard to predict. This is potentially a breaking change; for those upgrading, verify that you are calling start before using a microcosm.

9.14.1

9.14.0

Noticeable Changes

We improved the validation of stores to help improve debugging of bad inputs to Microcosm::addStore.

Internal Changes

9.13.1

9.13.0

Noticeable Changes

Upgrading

The enhancement Microcosm::addStore is not a breaking change, all old use cases will continue to work.

Those experimenting with app.history will need to rename calls to setFocus to checkout.

9.12.0

This is a big update, however there should be no breaking changes (assuming you are not referencing Microcosm internals).

Noticeable Changes

Internal Changes

Upgrading

For those referencing Microcosm internals, we have moved their hosted directory from src to the folder root. This means the following changes are necessary:

Instead of:

require('microcosm/src/lifecycle')

Change this to:

require('microcosm/lifecycle')

9.11.0

Noticeable Changes

9.10.0

Noticeable Changes

9.9.2

Internal changes

9.9.1

Noticeable changes

Internal changes

9.9.0

Noticeable changes

Internal changes

9.8.0

Noticeable changes

9.7.0

9.6.0

Noticeable changes

Internal changes

Upgrading

There are no breaking changes for this release.

9.5.0

9.4.1

Internal changes

9.4.0

Noticeable changes

Internal changes

Upgrading

This version adds lifecycle actions. This does not make any breaking change to the Microcosm API, however it provides us better internal consistency.

These lifecycle actions are still undergoing development (names may change, etc, but we’ll keep you posted). However if you would like to give them a spin, consider the following code example:

import { willStart } from 'microcosm/lifecycle'
import { addPlanet } from 'actions/planets'

const Planets = {
  reset() {
    return []
  },
  add(records, item) {
    return records.concat(item)
  },
  register() {
    return {
      [willStart] : Planets.reset,
      [addPlanet] : Planets.add
    }
  }
}

9.3.0

Noticeable changes

9.2.0

Noticeable changes

Internal changes

Upgrading

All changes are purely internal polish. There should be no additional required action. The build is about 100 bytes smaller, but who’s counting? :)

9.1.0

Internal changes

9.0.0

Noticeable changes

Breaking Changes

Upgrading

Foliage

For those using the Foliage API, consider using Foliage within Stores themselves.

app.push

app.push should continue to work as expected when only one parameter is pushed to an action, however those pushing multiple parameters should make the following change:

// Before:
app.push(action, 'one', 'two',' 'three')
// After:
app.push(action, ['one' ,'two', 'three'])

Additionally, the third argument of app.push is now an error-first callback. When an action resolves or fails, it will execute this callback:

app.push(action, params, function(error, body) {
  if (error) {
    handleError(error)
  }
})

Getting app state

All instances of app.get('key') should be replaced with app.state.key, sort of like if it were a React Component

8.3.0

Breaking changes

Upgrading

In the past, Microcosm would use requestAnimationFrame to batch together changes. However this can cause unexpected consequences when sequentially performing otherwise synchronous operations. For those who wish to preserve this behavior, consider using debounce to “choke” high frequency changes.

8.2.0

Internal Changes

Fixes

Upgrading

For those using Store.prototype.send, the following change is necessary:

// Before
store.send(state, action, payload)
// After
Store.send(store, action, state, payload)

8.1.0

Noticeable changes

Internal Changes

8.0.0

Noticeable changes

Breaking Changes

Changes to Stores

Before this release, stores would listen to actions using the stringified value of their functions:

var MyStore = {
  [Action.add](state, params){}
}

This was terse, however required actions to be tagged with a special helper method. It also required any module that needed access to a Store’s method to also know what actions it implemented.

To address these concerns, Stores now communicate with a Microcosm using the register method:

var MyStore = {
  register() {
    return {
      [Action.add]: this.add
    }
  },
  add(state, params){}
}

Under the hood, Microcosm tags functions automatically.

7.1.1

7.1.0

Noticeable changes

Internal improvements

7.0.0

6.2.1

6.2.0

6.1.0

6.0.0

6.0.0 is the second effort to reduce the surface area of the Microcosm API.

5.2.0

5.1.1

5.1.0

5.0.0

Version 5 represents an attempt to address some growth pains from rapidly adding new features to Microcosm. Names have been changed to improve consistency and internal APIs have been refactored. The overall surface area of the app has been reduced and more opinions have been made.

As an additional illustration, the Microcosm API has been logistically sorted within ./cheatsheet.html

4.0.0

3.3.0

3.2.0

3.1.0

3.0.0

2.0.1

2.0.0

More info on removing currying

Currying has been removed Microcosm::send. This was overly clever and somewhat malicious. If an action has default arguments, JavaScript has no way (to my knowledge) of communicating it. One (me) could get into a situation where it is unclear why an action has not fired properly (insufficient arguments when expecting fallback defaults).

In a language without static typing, this can be particularly hard to debug.

In most cases, partial application is sufficient. In light of this, actions can be “buffered up” up with Microcosm::prepare:

// Old
let curried = app.send(Action)

// New
let partial = app.prepare(Action)

Microcosm::prepare is basically just fn.bind() under the hood. Actions should not use context whatsoever, so this should be a reasonable caveat.

1.4.0

1.3.0

1.2.1

1.2.0

1.1.0

1.0.0

This version adds many breaking changes to better support other libraries such as Colonel Kurtz and Ars Arsenal.

In summary, these changes are an effort to alleviate the cumbersome nature of managing unique instances of Actions and Stores for each Microcosm instance. 1.0.0 moves away from this, instead relying on pure functions which an individual instance uses to operate upon a global state object.

0.2.0

0.1.0