Stateful Components
Describes what a stateful Component is and how to create it
A stateful component is a component tied to a state class that is used to keep its "state" during its lifetime.
A state is just a C# class with an empty constructor.
When a Component is first displayed on the page, i.e. the MAUI widget is added to the page visual tree, MauiReactor calls the method OnMounted()
.
Before the component is removed from the page visual tree MauiReactor calls the OnWillUnmount()
method.
Every time a Component is "migrated" (i.e. it is preserved between a state change) the OnPropsChanged()
overload is called.
OnMounted()
is the ideal point to initialize the component, for example calling web services or querying the local database to get the required information to render it in the Render()
method.
For example, in this code we'll show an activity indicator while the Component is loading:
and this is the resulting app:
Do not use constructors to pass parameters to the component, but public properties instead (take a look at the Components Properties documentation page).
Updating the component State
When you need to update the state of your component you have to call the SetState
method as shown above.
When you call SetState
the component is marked as Invalid and MauiReactor triggers a refresh of the component. This happens following a series of steps in a fixed order
The component is marked as Invalid
The parent and ancestors up to the root component of the page are all marked as Invalid
MauiReactor triggers a refresh under the UI thread that creates a new Visual tree traversing the component tree
All the components that are Valid are re-used (maintained in the VisualTree) while the components marked as Invalid are discarded and a new version is created and its
Render
method calledThe new component version creates a new tree of child nodes/components that are compared with the tree linked to the old version of the component
The old visual tree is compared to the new one: new nodes are created along with the native control (i.e. are mounted), removed nodes are eliminated along with the native control (i.e. are unmounted), and finally, nodes that are only changed (i.e. old and new nodes are of the same type) are migrated (i.e. native control is reused and its properties are updated according to properties of the new visual node)
In the end, the native controls are added, removed, or updated
For example, let's consider what happens when we tap the Increment button in the sample component below:
Let's now consider this revisited code:
When the button is clicked the variable State.Counter
is updated to 1 so the component is re-rendered and the Label
is umounted (i.e. removed from the visual tree) and the native control is removed from the parent VStack
Control list (i.e. de-allocated):
If we click the button again, the Label
component is found, again, in the new version of the Tree, so it's mounted and a new instance of the Label
component is created (along with the Native control that is created and added to the parent VStack
control list).
Updating the state "without" triggering a refresh
Re-creating the visual tree can be expensive, especially if the component tree is deep or the components contain many nodes; but sometimes you can update the state "without" triggering a refresh of the tree resulting in a pretty good performance improvement.
For example, consider the counter sample but with a debug message added that helps trace when the component is rendered/created (line 10):
Each time you click the button you should see the "Render" string output in the console of your IDE: this means, as explained, that a new Visual Tree has been created.
Now, imagine for example that we just want to update the label text and nothing else. In this case, we can take full advantage of a MauiReactor feature that lets us just update the native control without requiring a complete refresh.
Let's change the sample code to this:
Notice the changes to lines 15 and 20:
15: we use an overload of the Label()
the class that accepts a Func<string>
20: secondly we call SetState(..., invalidateComponent: false)
Now if you click the button, no Render message should be written to the console output: this proves that we're updating the native Label without
recreating the component.
Of course, this is not possible every time (for example when a change in the state should result in a change of the component tree) but when it is, it should improve the responsiveness of the app.
Last updated