state of component

What is “State?

There are 2 types of information a component can receive: Props and State.

Props is sent from parent and can not be changed during the component lifecycle.

State is what component maintains and encapsulates inside component. Parent does not need to know this information.

For example the rendering of checkbox, we keep this checkbox state as a state value. Click event can be received on component and component knows exactly what to do about these click events. In this case, no need to define callback to let parent to tell what to do, keep a state value inside component and use it for rendering.

Declare State

Exactly like @Props, just use @State before the state value. Can be used in @OnCreateLayout, @OnEvent, lifecycle methods.

Initialize State

@LayoutSpec
public class CheckboxSpec {

  @OnCreateInitialState
  static void createInitialState(
      ComponentContext c,
      StateValue<Boolean> isChecked,
      @Prop boolean initChecked) {

    isChecked.set(initChecked);
  }
}

Note that you don’t have to define an initialization. If you don’t, java default value will be used.

@OnCreateInitialState will only be called once when it’s added to componentTree. Layout recalculation will not call it again.

Define State Updates

@LayoutSpec
public class CheckboxSpec {

  @OnUpdateState
  static void updateCheckboxState(StateValue<Boolean> isChecked) {
    isChecked.set(!isChecked.get());
  }
}

You can have as many as @OnUpdateSate.

However, when there is a stateUpdate called, layout will be recalculated. So bundling multiple state updates will improve performance.

Call State Updates

@OnEvent(ClickEvent.class)
  static void onCheckboxClicked(ComponentContext c) {
    Checkbox.updateCheckboxAsync(c);
    // Checkbox.updateCheckbox(c); for a sync update
  }
  }

When calling state update, just need to pass in component context and all the Props defined in @OnUpdateState. No need to mention the state itself.

Keep in mind that calling state update will trigger a new layout recalculation. This will cause indefinite loop pretty easily. So try to avoid calling state update in onCreateLayout, bind, mount.

Keys to Identify Components

Autogenerated Keys

Framework will auto-generate keys for each component based on its type and the key of its parent. This key is used to finding the specific component when doing update.

If the same parent have multiple same type children, they will have same keys. Framework will auto-generate one based on the order that they are added.

However, if the order of components will change, then framework will fail to detect the right component. So if you expect your components to move around, you have to assign manual keys.

Manual Keys

@OnCreateLayout
static Component onCreateLayout(
    ComponentContext c,
    @State boolean isChecked) {

  final Component.Builder parent = Column.create(c);
  for (int i = 0; i < 10; i++) {
    parent.child(Text.create(c).key("key" +i));
  }
  return parent.build();
}

Lazy state Updates

When you want to update a state value but do not need to immediately trigger a layout recalculation, use lazy state update.

This is useful to hold some information internally in component for the use of next time rendering.

@OnCreateLayout
static Component onCreateLayout(
    final ComponentContext c,
    @State(canUpdateLazily = true) String foo) {

  FooComponent.lazyUpdateFoo(context, "updated foo");
  return Column.create(c)
      .child(
          Text.create(c)
              .text(foo))
      .build();
}

@OnCreateInitialState
static void onCreateInitialState(
    ComponentContext c,
    StateValue<String> foo) {
  foo.set("first foo");
}

Immutability

State has to be immutable (better to be primitive type) for the sake of background layout.