StateEngine
In the overview you saw states in action. Now we will break down the rule
structure and explain how
StateEngine
works. A state rule has this shape:
[ when, then, nested_or_metadata ]
You usually define them inside a component like this:
static states = [
[ when, then, metadata ],
...
];
Whenever Qite sees component has a static member called
state
it initializes
a
StateEngine
for it and feeds it all the rules listed in that
state
member.
The "when" part
The
when
part defines the conditions under which a state becomes active.
In practice, you will most often use the object form:
[ { status: "paid" }, ... ]
This means the state becomes active when
component.fields.get("status") === "paid". But you can also match multiple
fields:
[ { fields: { status: "shipped", tracking_number: "isPresent()" } }, ... ]
You can also use flags:
[ { flags: ["saving"] }, ... ]
[ { flags: ["!saving"] }, ... ]
There is a shorthand object form. If you omit explicit
fields
or
flags
keys, Qite infers them:
[ { status: "paid", saving: true }, ... ]
Here
status
is treated as a field and
saving
as a flag (because it was
declared in
static flags). If a flag value is a function, it becomes
a flag matcher:
[
// here, c is a component instance and v is current flag value
{ saving: (c, v) => v === true },
...
]
You can also use a function as the condition:
[
(c) => c.fields.get("status") === "paid" &&
c.flags.get("saving") === false,
...
]
Or an array of functions. In that case, all functions must return true. And a state itself becomes active only if all its conditions (and each function in each condition) evaluate to true.
The "then" part
The
then
part defines transitions — those run when a state changes
its active status. When the state becomes active, its "in" transitions run.
When the state becomes inactive, its "out" transitions run. In its simplest form
it's just a single function, which is treated as an "in" transition:
[ { status: "paid" }, (c) => c.generateInvoice() ]
But you can explicitly define both "in" and "out" transitions:
[
{ status: "submitted" },
{
in: (c) => c.flags.set("saving", true),
out: (c) => c.flags.set("saving", false)
}
]
Or pass an array of functions, which are all treated as "in" transitions:
[
{ status: "paid" },
[
(c) => c.generateInvoice(),
(c) => c.logAnalytics()
]
]
Each transition receives the component instance as its only argument.
Nested rules
Nested rules let you share conditions and transitions without repeating them. Consider this code:
[
{ status: "submitted" }, (c) => c.logSubmit(),
[
[ { flags: ["saving"] }, (c) => c.showSpinner() ],
[ { flags: ["!saving"] }, (c) => c.hideSpinner() ]
]
]
The parent's condition
{ status: "submitted" }
applies to all nested rules.
Likewise, the parent's transition
logSubmit()
is inherited as well.
The code above effectively translates into the following two states internally:
[
[
{ fields: { status: "submitted" }, flags: ["saving"] },
[(c) => c.showSpinner(), (c) => c.logSubmit()]
], [
{ fields: { status: "submitted" }, flags: ["!saving"] },
[(c) => c.hideSpinner(), (c) => c.logSubmit()]
]
]
The first notation literally translates into the two states shown in the
second option. No standalone rule like
[{ status: "submitted" }, (c) => c.logSubmit()]
is implied. And even though in this particular example it doesn't matter, because
flag is always
true
or
false, it may be important to remember
when writing more complex examples.
Nested rules help you structure related logic without duplicating the outer conditions.
Metadata
The third element of a rule can also be a metadata object:
[ { status: "paid" }, (c) => c.generateInvoice(), { name: "paid" } ]
// -----------------------------------------------|------^-------|--
metadata
Metadata can assign a name (useful for debugging), adjust priority, or control advanced behavior such as eligibility and overlay semantics. We will return to these advanced topics later.
How StateEngine evaluates states
Whenever a field or flag changes, the engine reevaluates all rules from scratch. It computes which states are active, compares them to the previous set, and runs the appropriate transitions. States that became inactive run their "out" transitions, states that became active may run their "in" transitions depending on their eligibility and ordering.
You never manually track previous values. The engine handles activation,
deactivation, ordering, and transition execution. In the next section we
will look at
DisplayStateEngine, which uses the same rule format but interprets
transitions as visibility targets.