State
The State is for component instances to store changing data. This could include anything from text a user entered into a form, data received from an API, or data that represents UI flow changes, such as the visibility of a modal. For more discussion of State, see Ramping Up with Modulo - Part 2.
By default (that is, with no -store
attribute), state data is unique to every
component instance, and components can never directly access sibling or parent
data. It is possible to indirectly reference it, however, by passing state data
from a "parent" component to a "child" components within the parent by passing
it via a Props attribute. In this case, the data should be considered
read-only to the child component, like any other Props data.
Example
See below for a quick example, showing off an example of each of the 6 types of data:
Definition
State is traditionally included in Component definitions below the Template tag, but above the Script tag. This makes sense because functions in the Script tag typically manipulate state in order to render new HTML in the Template, making Script a sort of mutable bridge between Script and Template. State is defined in a similar way to Props: Only defined with properties, but no contents.
See below for an example of defining a State CPart:
Example 1
Two State variables specified, of type String and Number:
Example 2
Building up complicated JSON data with "." syntax:
Note that all "state variables" must have an initial value. It's okay to make
the initial value be null
(as in the "billable" example above), or other some
placeholder that will later be replaced. Undefined state variables are treated
as errors.
Stores
If you want to share data between components globally, such that any component
can modify the data causing a re-render to all linked components, such as user
log-in information or global state data, then you can use the powerful -store
attribute:
With this, any state with the given store throughout your application will share state and subscriptions to updates.
Limiting a store with -only
Sometimes, you'll only want to subscribe to certain attributes parts of a store:
CPart properties
The actual data in a State CPart is stored on it's "data" property. This
property is a regular JavaScript Object, and thus can store any JavaScript data
type. As an example, in a Script CPart, you can directly reference this
property with the code cparts.state.data
.
When writing the State CPart definition, you must declare or pre-define each
"state variable" or property of the "data" Object that you want to use. It is
not permitted to create new state variables later on. In other words, if you
only define cparts.state.data
as having .count
and .title
as "state
variables" (aka properties of the "data" Object), then an attempt like
cparts.state.data.newstuff = 10;
may result in an error. If you are dealing
with a situation where you have an unknown quantity of data, such as from an
API, the correct approach is to store it all within a nested Object inside
the state data Object, e.g. such as an data.apiResults
Object or Array.
Unlike top-level "state variables", it's okay to add properties, modify, or
build upon nested Objects.
While it's allowed to assign any arbitrary reference as a State variable,
including complex, unserializable types such as function callbacks, it's highly
recommended to try to keep it to primitive and serializable types as much as
possible (e.g. String, Number, Boolean, Array, Object). The reason being that
there may be future features or third-party add-ons for State which will only
support primitive types (as an example, that would be required to save state to
localStorage). If you want to store functions, consider using a
prepareCallback
to generate the functions within a Script context, and only
store the data needed to "generate" the function in the state (as opposed to
storing a direct reference to the function itself).
renderObj
State contributes it's current data values to the renderObj. Examples:
- State initialized like:
<State name="Luiz">
will be accessible on the renderObj likerenderObj.state.name
, and in the Script or Template CParts likestate.name
. - State initialized like:
<State stuff:='["a", "b"]'>
will be accessible on the renderObj likerenderObj.state.info
(with individual items accessed with code that ends with ".stuff[0]
"), and in the Script or Template CParts likestate.info
.
Directives
State provides a single directive:
[state.bind]
- Two-way binding with State data, with the key determined by thename=
property of whatever it is attached to. You can attach a[state.bind]
directive to any HTML<input>
, and the State CPart's two-way binding will cause the input value to be updated if the state variable ever changes, and if a user edits the input triggering a"keyup"
or"change"
event, the state variable will be updated (along with, typically, a re-render of the component).
Syntax Examples
Examine below for how two different syntaxes can be used to construct data: Either the JSON style all in one go, or the somewhat more verbose (but perhapse easier to maintain) dataProp style:
Binding Examples
- Useful resource: Read this for a full list of input types. With the exception of some of the ones listed below, they will all be "String" in terms of the State CPart. MDN input Element documentation
Example #1: Binding different input types
Example #2: Combining with filters
Example #3: Specifying events
Store Examples
Example #1: Store for simple state sharing
Example #2: Multiple States and bound buttons
Here we have an incomplete "chat" component with two State CParts. This one shares state between instances of it. Note that "msg" is not shared (neither is Props), but "messages" is shared.