Skip to content
This repository was archived by the owner on May 22, 2024. It is now read-only.

Authoring components

Alexander Semenov edited this page Aug 5, 2015 · 25 revisions

How Morearty is implemented

Morearty.js is implemented as a React mixin. Mixins allow to enrich components API, i.e. methods defined on a mixin become available on a component instance.

So, every Morearty component should include:

mixins: [Morearty.Mixin]

in its class declaration.

Morearty listens for state changes and triggers re-rendering from the top. Since components get shouldComponentUpdate implementation based on the state they bind to, only components whose state was altered are re-rendered.

Passing bindings to sub-components

Binding is a central concept in Morearty. As of Morearty 0.7.8 there are several ways to pass bindings to sub-components and make them eligible for auto-generated shouldComponentUpdate. Both approaches can be freely combined.

Using an attribute

Components can receive bindings to their state section using binding attribute:

<MyComponent binding={ binding.sub('sub.path') } />

Attribute name can be set to a different value by passing appropriate option to Morearty.createContext method. See API documentation for details.

See Multi-binding components and default binding section below if you need to pass multiple bindings.

Using observed bindings

Morearty 0.7.8 introduced new less intrusive way of passing bindings around inspired by Om's reference cursors. It's less strict and is simpler for refactoring and experiments.

In a nutshell, instead of relying on binding attribute, just store your bindings in a shared module and add any required bindings to component's observedBindings array field:

{
  observedBindings: [SharedBindings.prefsBinding],
  render: function () {
    var prefs = SharedBindings.prefsBinding.get();
    // ...
  }
}

Alternatively, use component's observeBinding method which will register binding as observed for you:

{
  render: function () {
    this.observeBinding(SharedBindings.prefsBinding);
    var prefs = SharedBindings.prefsBinding.get();
    // ...
  }
}

Or in functional style:

{
  render: function () {
    return this.observeBinding(SharedBindings.prefsBinding, function (prefs) {
      // ...
    });
  }
}

This approach feels more natural and is easier to work with in many scenarios.

Components API

In addition to casual component API methods Morearty provides the following capabilities.

Getting Morearty context

Morearty.js context is available throught getMoreartyContext method:

var ctx = this.getMoreartyContext();
// do whatever you want with context

See dedicated page on context for a set of available operations on it.

Getting component's binding

Component's bindings can be retrieved using getDefaultBinding or getBinding methods:

var binding = this.getDefaultBinding();
var someValue = binding.get('sub.path');
// or
var namedBinding = this.getBinding('name');
var someOtherValue = namedBinding.get('sub.path');

See the section on multi-binding components for a difference between these two.

Listening for state changes

Components can leverage lifecycle bound changes notification using addBindingListener method:

componentDidMount: function () {
  this.addBindingListener(this.getDefaultBinding(), 'optional.sub.path', function (changes) { /* ... */ });
}

The advantage of this approach in comparison with manual listener registration is that you don't need to keep listener id and remove the listener in componentWillUnmount - Morearty will do all this for you.

Auto-defined services components provide

Auto-defined shouldComponentUpdate

Morearty components get correctly defined shouldComponentUpdate method that compares the component's state using straightforward JavaScript strict equals operator ===. This gives good performance without any additional coding. See dedicated section below if you need to override the default behavior.

Default state publication

Often, component needs to initialize its state on mount. In Morearty model, when component is mounted, its state may already contain some data. For example, you can persist application state to local storage by converting it to transit-js format and restore it on start (see [this](Serializing application state) section for examples). For this to work Morearty supports four merge strategies out of the box and the custom one:

  • Morearty.MergeStrategy.OVERWRITE - overwrite existing state;
  • Morearty.MergeStrategy.OVERWRITE_EMPTY - overwrite if existing state is empty (undefined or null);
  • Morearty.MergeStrategy.MERGE_PRESERVE (default) - perform deep merge preserving existing values;
  • Morearty.MergeStrategy.MERGE_REPLACE - perform deep merge replacing existing values;
  • custom function accepting currentState, defaultState and returning merge result.

To initialize component's state on mount declare getDefaultState method:

getDefaultState: function () {
  return Immutable.Map({
    name: null,
    status: '...'
  });
}

or for multi-binding component:

getDefaultState: function () {
  return {
    default: Immutable.Map({
      name: null,
      status: '...'
    }),
    language: 'en'
  };
}

You can customize merge strategy by declaring getMergeStrategy method:

getMergeStrategy: function () {
  return Morearty.MergeStrategy.OVERWRITE;
}

or for multi-binding component:

getMergeStrategy: function () {
  return {
    default: Morearty.MergeStrategy.MERGE_PRESERVE,
    language: Morearty.MergeStrategy.OVERWRITE
  };
}

Default meta-state publication

In the same manner default meta state publication is supported since 0.7.12. The only difference is method name: getDefaultMetaState instead of getDefaultState. Both methods share common merge strategy defined using getMergeStrategy.

Custom shouldComponentUpdate

If customized shouldComponentUpdate is needed, declare shouldComponentUpdateOverride method accepting original shouldComponentUpdate, nextProps, and nextState, e.g.:

shouldComponentUpdateOverride: function (shouldComponentUpdate, nextProps) {
  return shouldComponentUpdate() ||
    (this.props && nextProps && this.props.language !== nextProps.language);
}

Multi-binding components and default binding

For some components single binding may be not enough. For example, you display some data but display language is set globally in other state section. You can choose to pass language as an attribute and override shouldComponentUpdate method as above (if you don't do this, the component won't be re-rendered on attribute change). Alternatively, you can supply multiple bindings to your component in JavaScript object:

render: function () {
  return MyComponent({ binding: { default: defaultBinding, language: languageBinding } });
}

When checking for modifications every component's binding will be assumed.

To comfortably extend your components to multiple bindings default binding concept is introduced. You start with single binding and acquire it using this.getDefaultBinding() method which always return single binding for single-binding components (no matter how it was passed - directly or in an object), or binding with key default (hence the name) for multi-binding components, or else first observed binding, if any. When you move to multiple-binding you access your auxiliary bindings with this.getBinding(name) method while existing code stays intact:

var binding = this.getDefaultBinding(); // no changes required
var languageBinding = this.getBinding('language');
var language = languageBinding.val();
// ...

As an alternative, consider using observed bindings introduced in 0.7.8.

Component skeleton to start with

Below is a component skeleton you can copy-paste and start play with:

var MyComponent = React.createClass({
  displayName: 'UnnamedComponent', // TODO give name
  mixins: [Morearty.Mixin],

  render: function () {
    return null; // TODO render something
  }
});
  • Home
  • Essential concepts
    • Binding
    • Context
    • [Asynchronous rendering](Asynchronous rendering)
    • [Authoring components](Authoring components)
  • How to
    • [Bootstrap the app](Bootstrapping the app)
    • [Render on server](Server rendering)
    • [Serialize application state](Serializing application state)
    • [Use history module](Working with history)

Clone this wiki locally