Skip to main content

Composition Order and the Dispose Chain

withMixins states the rule for composition order: mixins are applied right to left, so withMixins(withXxx, withYyy, withZzz, Base) is equivalent to withXxx(withYyy(withZzz(Base))) — withZzz is applied first (its generated class sits closest to Base), and withXxx is applied last (its generated class sits closest to the final, most-derived instance).

That fact has a direct, practical consequence for dispose() that's worth spelling out explicitly, because it's easy to compose mixins correctly and still get cleanup ordering wrong without realizing it.

Working Out the Order​

Every mixin covered so far follows the same shape for dispose():

dispose() {
// 1. this mixin's own cleanup runs first
this[managedResource].forEach(r => r.destroy())
// 2. then it hands off to whatever's beneath it
super.dispose()
}

Given withMixins(withXxx, withYyy, withZzz, Base), the resulting prototype chain — from the actual instance outward toward Base — is:

xxxDerived → yyyDerived → zzzDerived → Base (Component)

JavaScript method resolution always starts at the most-derived class. So calling instance.dispose() runs in this order:

  1. xxxDerived's own cleanup code runs first (everything in its dispose() before the super.dispose() call)
  2. It calls super.dispose(), which runs yyyDerived's own cleanup
  3. Which calls super.dispose(), running zzzDerived's own cleanup
  4. Which finally calls super.dispose(), reaching Component's own base dispose() (removing the element from the DOM, disposing the underlying Store) last

In short: the mixin listed first in withMixins(...) has its cleanup logic run first; Component's own base disposal always runs last, regardless of how many mixins sit in between.

Why This Matters​

Most of the time, mixin cleanup routines are independent of each other — withForm's form teardown doesn't care whether withA11y's focus trap has already been deactivated. When that's true, order is invisible and nothing here needs any attention.

It stops being invisible the moment one mixin's cleanup logic depends on something another mixin manages. For example, imagine a mixin whose dispose() needs to read a DOM attribute that a differently-ordered mixin's own dispose() has already removed by the time it runs — the outcome now silently depends on which order the two were passed to withMixins, and swapping that order changes behavior with no type error and no warning.

If you're authoring a mixin whose cleanup interacts with state another mixin might also be managing, document that dependency explicitly (in a code comment at minimum, in the README ideally) rather than leaving it to be discovered when someone composes mixins in an order that happens not to work.

A Note on Composing Many Mixins​

Each mixin in a chain adds one more constructor frame (via super(...args)) and one more link in the dispose() chain. For the handful of mixins a typical component realistically combines — accessibility, one or two resource-creating mixins, maybe a form — this overhead is negligible. It's worth knowing about mainly so that an unusually deep composition chain (many mixins stacked on a single class) doesn't come as a surprise if you're ever profiling construction or teardown cost; the depth of the withMixins(...) argument list is a direct, visible proxy for how many constructor/dispose frames a given component pays for on every instantiation and disposal.