Data Elements
Description
The DataElement base class provides for a Flux/Redux style unidirectional data flow state management
pattern using DOM events and custom elements.
By utilizing the DOM and custom elements, the footprint is small and performance is fast since communication happens through DOM events and not a JavaScript library.
It works well with LitElement since that also uses custom elements,
but since it is a custom element itself, it will work with any (or no)
library/framework.
See: State Management
Basic Usage
This is a contrived example showing default usage of a DataElement;
import { customDataElement, DataElement, event } from "@domx/dataelement";
@customDataElement("session-data", {
eventsListenAt: "window"
});
export class SessionData extends DataElement {
static defaultState = {
loggedInUserName: "",
loggedInUsersFullName: ""
};
state = SessionData.defaultState;
// event creator
static userLoggedInEvent = (userName, fullName) =>
new CustomEvent("user-logged-in", {
bubbles: true,
composed: true,
detail: {userName, fullName}
});
@event("user-logged-in")
userLoggedIn({detail:{userName, fullName}}) {
this.state = {
...this.state,
loggedInUserName: userName,
loggedInUsersFullName: fullName
};
this.dispatchEvent(new CustomEvent("state-changed"));
}
}
UI Component
Basic Usage
This is a contrived example showing default usage of a DataElement.
import { DataElement } from "@domx/dataelement";
import { customDataElement, event } from "@domx/dataelement/decorators";
export class UserLoggedInEvent extends Event {
static eventType = "user-logged-in";
userName:string;
fullName:string;
constructor(userName:string, fullName:string) {
super(UserLoggedInEvent.eventType, {
bubbles: true,
composed: true
});
this.userName = userName;
this.fullName = fullName;
}
}
@customDataElement("session-data", {
eventsListenAt: "window"
});
export class SessionData extends DataElement {
static defaultState = {
loggedInUserName: "",
loggedInUsersFullName: ""
};
state = SessionData.defaultState;
// event comes from the EventMap package
@event(UserLoggedInEvent.eventType)
userLoggedIn(event:UserLoggedInEvent) {
this.state = {
...this.state,
loggedInUserName: event.userName,
loggedInUsersFullName: event.fullName
};
this.dispatchEvent(new Event("state-changed"));
}
}
By subclassing the Event class, The
UserLoggedInEventacts as a great way to document what events a data element can handle. This is similar to action creators in Redux. They can be defined in the same file as the DataElement (or in a separate file if that works better for you) and used by UI components to trigger events.
The static
defaultStateproperty allows UI components to reference thedefaultStatefor initialization.
UI Component
The SessionData element can be used in any UI component.
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { linkProp } from "@domx/dataelement";
import { SessionData, UserLoggedInEvent } from "./SessionData";
class LoggedInUser extends LitElement {
state = SessionData.defaultState;
render() {
const state = this.state;
return html`
<session-data
@state-changed="${linkProp(this, "state")}"
></session-data>
<button @click="${this.updateUserClicked}">Update user</button>
<div>
Logged in as: ${state.loggedInUserName}
(${state.loggedInUsersFullName})
</div>
`;
}
updateUserClicked(event) {
this.dispatchEvent(new UserLoggedInEvent("juser", "Joe User"));
}
}
linkProp is a helper method to propagate changes from a data element to its parent UI element. See linkProp.
Highlights
- Works with Redux Dev Tools.
- Can configure any (and multiple) properties to be a
stateproperty. - Can use/configure a
stateIdproperty to track state for instance data. - Works with (but does not require) the StateChange monad for
functionalJavaScript patterns (e.g.reducers)StateChangealso works with Immer which eliminates object creation fatigue when working with immutable state.
- Uses EventMap for declarative DOM event handling on custom elements.
- Top question: “can it really be that simple?”