Note: this chapter is modelled after RectJS/State and Lifecycle article. It is conceptionally close but uses different terminology.

Data [Model] and Component Lifecycle

Sciter's Reactor, in general, is all about components that employ Model-View-Controller idiom.

You have some data to be presented to the user. User is able a) to view the data and manipulate it. Manipulation of data is a set of code functions and event handlers that, as a group, are named as Controller.

Component Class

Let's transform our ticking clock sample into reusable Clock component.

Each Reactor class based component shall have render() method:

class Clock : Element {
  function render() {
    return 
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.time.toLocaleString(#time)}.</h2>
      </div>;
  }
}

The render() method will be called each time an update happens, but as long as we render <Clock /> into the same DOM node, only a single instance of the Clock class will be used. This lets us use additional features such as local state and lifecycle methods.

Adding Local Data Storage to a Class

By convention local data storage of a component is located in instance's this.data field:  

class Clock: Element 
{
  // declaring local data storage with initial value 
  this var time = new Date(); 

  function render() {
    return
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.time.toLocaleString(#time)} now.</h2>
      </div>;
  } 
} 

and our render() method renders now content of this properties.

Class Clock, in its current version, just renders static time determined at the time of its creation. Let's transform it to a running clock.

Adding Lifecycle Methods to a Class

We want to set up a timer whenever the Clock is rendered to the DOM for the first time - when real DOM element is created. In ReactJS this is called “mounting”. Sciter uses "attached" term for that - function attached() is called when instance of a class is attached to the DOM element:

include "sciter:reactor.tis";

class Clock: Reactor.Component 
{
  this var time = new Date(); // setting initial state 

  function attached() { // a.k.a. React::componentDidMount
    this.timer(1s, function() {
      this.update { time:new Date() };
      return true; // to keep the timer ticking
    });
  }

  function render() {
    return
     <div>
      <h1>Hello, world!</h1>
      <h2>It is {this.time.toLocaleString(#time)} now.</h2>
     </div>;
  } 
} 

You see here two new entities:

  1. Use of Reactor.Component class that is declared in sciter:reactor.tis script.
  2. And implementation of the attached() method that starts ticking timer.

The timer callback function is where the magic happens:

function() {
  this.update { time:new Date() };
  return true; // to keep the timer ticking
}

On each timer tick it calls Reactor.Component.update(newdata) method with the new data. 

Note: Sciter out of the box provides two lifecycle methods:

And now it is a time to check what that Reactor.Component class is.

Class Reactor.Component

The Reactor.Component class is defined in "sciter:reactor.tis" resource that is an integral part of Sciter Engine, so it is always available.

The class is very simple - it introduces just one method update(newdata):

namespace Reactor 
{
  class Component : Element
  {
    function update(newdata = null) {
      assert !newdata || typeof newdata == #object; 
      if(newdata) this.extend(newdata);
      this.post(() => this.merge(this.render()),true);
    }
  }
}

As you see the update(newdata) does the following:

  1. this.extend(newdata) - this merges newdata with properties of this object;
  2. function updater() { this.merge(this.render()); } - defines local update method that
    1. this.post(updater) - schedules the updater() invocation to the next event processing cycle.

    The delayed rendering logic ensures that multiple updater() execution requests are collapsed into single updater() invocation request in the event queue.

    Using Data Correctly

    There are three things you should know about update(newdata).

    Do Not Modify the Data (state) Fields Directly

    For example, this will not re-render a component:

    // WRONG!
    this.comment = "Hello";

    Instead, use update():

    // Correct
    this.update {comment: "Hello"};

    The only place where you can assign properties directly is the constructor.

    Data Updates are Merged

    When you call update(newdata), Reactor.Component merges the object you provide into the current data set.

    Therefore your code may issue update() requests for different parts of the data and they will end up in single reconciliation action between real DOM element and result of render() call, so these:

    clock.update { time:new Date() };
    ...
    clock.update { greeting: "Вася" };

    will end up in single clock.render() call and screen update.

    Comment to the above. In Sciter, this: 

    obj.method { name:val };

    is a short form of a method call:

    obj.method({ name:val });

    Just a syntax sugar for making our developer's life a bit easier.