This repository has been archived by the owner. It is now read-only.
Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
163 lines (122 sloc) 4.66 KB
title description layout weight
State
guide
110

The State class provides a way of defining state properties for the classes that extend it, as well as watching these properties for value changes.

The Component class already extends from State by default, besides automatically rerendering when there is a change.

If your class doesn't need to render anything it's best to extend from {sp}State directly though. That way you'll have access to its features without also inheriting logic you won't need.

Configuring State

The following example is a class that extends directly from State and defines a state property named number on itself:

import core from 'metal';
import State from 'metal-state';

class Calculator extends State {
    /**
     * Coverts string numbers to the number type.
     */
    setNumber(val) {
        if (core.isString(val)) {
            val = parseInt(val, 10);
        }
        return val;
    }
}

Calculator.STATE = {
    number: {
        // Called whenever a new value is set. Useful when normalizing your
        // state data.
        setter: 'setNumber',

        // Accepts either number or string types. If the validator check fails,
        // the new value is discarded, and the current value kept.
        validator: val => core.isNumber(val) || core.isString(val),

        // Initial value
        value: 0,

        // You can, instead of the `value` option above, use a function to 
        // return the initial value for the state.
        valueFn: val => 0,

        // It's also possible to define that a property can only receive a 
        // value once, and later behave as read-only.
        writeOnce: false
    }
}

If you're familiar with YUI, you may recognize this feature and notice that it's very similar to how attributes are defined there. You basically just need to list all attributes you'll be using on the {sp}STATE static variable (on YUI it would be on ATTRS), and provide their configuration options, like initial value and validator. For a list of all valid options, take a look at State's {sp}docs.

Internal States

You can define a state as internal, for use in your component only. Just add the attribute internal to the configuration object.

By doing this, the state will behave exactly as expected, including re-rendering the component. The only difference being, internal states can not be accessed by parent components.

This is not necessary for JSX components, since Metal.js JSX components have their own State Manager implementation. For that reason, two static properties are used, STATE and PROPS, that behave much closer to React's {sp}state and props.

To see exactly how it works read JSX components section.

Calculator.STATE = {
  number: {
    ...
    internal: true
  }
}

Accessing and Updating State

The constructor can receive a configuration object with initial values to use for its state properties. You can access or change an object's state in the same way you'd access or change any object property. Or you can also call the {sp}setState function, which updates the properties specified by the given object.

var obj = new Calculator();
console.log(obj.number); // Prints 0

obj.number = '1';
console.log(obj.number); // Prints 1

obj.setState({number: 2});
console.log(obj.number); // Prints 2

You can also track state value changes by listening to the appropriate event.

obj.on('numberChanged', function(event) {
    // event.prevVal has the previous value.
    // event.newVal has the new value.
});

To see all features of the State class take a look at its {sp}unit tests.

Configuration Data

Any data passed to the constructor that has not been configured as a state property can still be accessed via config. Changes to these properties won't be tracked, so it's usually intended for your component's options, which are only set from the outside.

var obj = new Calculator({
    number: 10,
    foo: 'foo'
});

console.log(obj.number); // Prints 2
console.log(obj.foo); // Prints undefined
console.log(obj.config.foo); // Prints 'foo'