Passing data up and down the component tree in LitElement, JavaScript, and TypeScript

This article is meant for people, who are only starting their journey with Lit library (recent merge of two libraries: LitHtml and LitElement).

Anastazja Galuza
3 min readMay 21, 2021

You are probably reading this because you are considering building a simple application, you probably even saw the Lit documentation and playground. If you already have some experience with React.js, you might see certain similarities between these two libraries when it comes to passing the data down the components, however passing the data up — from the child component to the parent, is a bit different.

In our example, let’s say you are building a small component called TextDisplay, with a custom text input field and an HTML-native paragraph; <p>-element. You want a user to type something into the input and that value to appear in the paragraph beneath. You want all inputs in your application to look exactly the same and be able to take some initial value, so you created a custom <text-input> component.

@customElement('text-input')
export class TextInput extends LitElement {
static styles = css`input { border: none;}`;

@property() oldValue = "";
dispatchInput(e) {
this.dispatchEvent(new CustomEvent("new-value", {
bubbles: true,
detail: e.target.value}));
}
render() {
return html`<input @input="${this.dispatchInput}" value="${this.oldValue}" type="text">`;
}
}

Passing the data down to these components is fairly simple. As you can see, in our TextInput class component we create a property called oldValue, which as default is an empty string. Then we pass this.oldValue to the value of the input, nested within our custom TextInput component.

In order to pass the data up from the TextInput, you need to dispatch the input event. When the native input event happens, you use the value of the event target and dispatch it with the new, custom event with the name of your preference. You need to set “bubbles” to true, because that means, that the event can go up the component tree. To the detail, you set the value of the event target — in our case, the text input.

If you are only programming in JavaScript, this is perfectly fine and will work for you, but for those of you who use TypeScript: here is an example of how to handle the same component:

@customElement(‘text-input’)
export class TextInput extends LitElement {
static styles = css`input { border: none;}`;

@property() oldValue: string = “”;
dispatchInput(e: InputEvent) {
const target = e.target as HTMLInputElement;
this.dispatchEvent(new CustomEvent(“new-value”, {
bubbles: true,
detail: target.value}));
}
render() {
return html`<input @input=”${this.dispatchInput}” value=”${this.oldValue}” type=”text”>`;
}
}

In the parent component, we will do the two following things: pass some data to the oldValue property and pick up on the dispatched value in the parent component in order to pass it onto our paragraph.

@customElement(‘text-display’)
export class TextDisplay extends LitElement {
static styles = css`p { color: blue }`;
@property()
name = ‘’;
updateName(e) {
this.name = e.detail;
}

render() {
return html`
<text-input oldValue=”My old value”
@new-value=”${this.updateName}”></text-input>
${this.name.length > 0
? html `<p>${this.name}</p>`
: undefined}`;
}
}

As you can see, you can just use the property created in the TextInput as an attribute on the <text-input> tag, for passing the data down. It is important to add, that even though in this example we are only passing down a string, it is also possible to pass other data types like arrays or objects. In that case, you would have written the property with a dot, like this:

<some-element .customProperty=”${this.myArrayWithObjects}”></some-element>

Now, let’s take a look at passing the data up. We pick up on the new-value custom event, that we just created and assign the detail from that event to the value we are passing to the paragraph. Until the value has been assigned to the name property, the paragraph will not be rendered. And again, here is a TypeScript version of our example:

@customElement(‘text-display’)
export class TextDisplay extends LitElement {
static styles = css`p { color: blue }`;
@property()
name: string = ‘’;
updateName(e: CustomEvent) {
if (e.detail != null) {
this.name = e.detail;
}
}

render() {
return html`
<text-input oldValue=”My old value”
@new-value=”${this.updateName}”></text-input>
${this.name.length > 0
? html `<p>${this.name}</p>`
: undefined}`;
}
}

If you need to pass the data further up, to some other component with is a parent to this parent, all you need to do is re-dispatch that custom event all over again and pick up on it in the same way in the parent of the TextDisplay component.

You are welcome to you can play around with this exact example and it out on the Lit playground.

--

--

Anastazja Galuza

Software Developer at an international corporation, a published author of “Anastasis”, a psychology enthusiast (5 years of studies) and a cat owner.