Skip to main content

Dealing With Dynamic Data

Where is My State?

Unlike in React, components using the FSComponent framework have no built in this.state or setState() method, because they do not have the concept of automatic re-rendering. Re-rendering components or portions of the tree is intentionally left to the developer; this gives the greatest ability to avoid stutter-causing garbage collection pauses triggered by DOM thrashing or the discarding of large numbers of virtual DOM nodes during the re-render and reconciliation process normally undergone by React.

However, manually re-rendering or updating DOM nodes can be a painful process, so FSComponent ships with a way to handle lots of usual cases.

Thinking in Subscriptions

FSComponent and the avionics framework are geared towards thinking of data as something that is pushed to you, as opposed to something that is queried from inside components. This allows components to be notified when there are substantive changes and only take action when action needs to be taken.

The avionics framework has a number of observable types available, all under the umbrella of the interface Subscribable (for readers) and class Subject (for readers and writers). These types can be subscribed to by consumers, and then will be notified when changes to the value occur.

For example, you can create a Subject on a numeric value, and then alert when the value changes:

const numberValue = Subject.create<number>(0);
numberValue.sub(value => console.log(value));

numberValue.set(5); //Logs '5'

Or, even create values that have different input and output types:

const humanReadableNumber = ComputedSubject.create<number, string>(0, num => num === 5 ? "Five" : "Not Five");
humanReadableNumber.sub(value => console.log(value));

humanReadableValue.set(6); //Logs 'Not Five'

Creating Subscribable Props

Taking our MyComponent example, we can create a prop that accepts a subscribable version of the value instead of a static value by replace the simple string type with Subscribable<string>:

interface MyComponentProps extends ComponentProps {
text: Subscribable<string>;
}

And then in our MyInstrument class:

public connectedCallback(): void {
super.connectedCallback();

const text = Subject.create<string>('Hello MSFS!');
FSComponent.render(<MyComponent text={text} />, document.getElementById('InstrumentContent'));

text.set('Hello, subscribers!');
}
info

Using Subscribable and not the concrete Subject types in your props interface definition is highly recommended. Using Subject can allow the component itself to write value updates, which is often not what is desired.

You'll notice that we didn't change MyComponent's render() function at all: it still has this.props.text in the same place that it did before. Yet, if you rebuild/resync this, you'll notice that the text now reads "Hello, subscribers!". How did this happen?

In FSComponent, subscribable values can appear within HTML elements in JSX and will be automatically re-rendered at their current DOM location, as long as they possess a toString() method. Additionally, you can place subscribable values in place of HTML element attribute values, as long as the underlying value types match, and those attributes will also be automatically updated when the subscribable value notifies of a change. However, you cannot use a subscribable value as a child of a component to get automatic re-rendering:

const subscribable = Subject.create<number>(0);

//Valid
public render(): VNode {
return (
<div>{subscribable}</div>
);
}

public render(): VNode {
return (
<div height={subscribable}>Some Text</div>
);
}

//Invalid
public render(): VNode {
return (
<SomeComponent>{subscribable}</SomeComponent>
);
}