typescriptcustom-componentstate-managementlit-elementlit-html

How can I get component state change in another component with litElement?


I started a project based on LitElement There are many components nested in each other, Let us say we have this structure:

enter image description here

the root component is my-app

import { LitElement, html, customElement, query } from 'lit-element';
import './my-form';
import './my-view';
import { MyView } from './my-view';

@customElement('my-app')
export class MyApp extends LitElement {
  @query('my-view') private myView?: MyView;

  private handleCountChange(e: CustomEvent<{ count: number }>) {
    if (!this.myView) throw 'my-view not found!';
    this.myView.count = e.detail.count;
  }

  render() {
    return html`
      <my-form @countChanged=${this.handleCountChange}></my-form>
      <my-view></my-view>
    `;
  }
}

as you see we have two components: my-form

import { LitElement, html, customElement, property } from 'lit-element';

@customElement('my-form')
export class MyForm extends LitElement {
  @property({ type: Number }) count: any = 0;

  private updateCount(e: KeyboardEvent) {
    this.count = (<HTMLInputElement>e.target).value;
    this.dispatchEvent(
      new CustomEvent('countChanged', {
        composed: true,
        bubbles: true,
        cancelable: true,
        detail: { count: this.count }
      })
    );
  }

  render() {
    return html`
      <input value=${this.count} @input=${this.updateCount} type="text" />
    `;
  }
}

and my-view:

import { LitElement, html, customElement, property } from 'lit-element';

@customElement('my-view')
export class MyView extends LitElement {
  @property({ type: Number }) count: number = 0;

  render() {
    return html`
      <p>${this.count}</p>
    `;
  }
}

To get the property count changes from my-form into my-view I dispatched event listener then used it at my-app then at handleCountChange I'm assigning the count value to MyView which imported as a class in addition to import it as a componet.

Currently, this works, but I feel it is a long way especially when I have more nested components. I would like to know if there is a better for doing that. Is there something similar to Context API which exists at react.js I thought about using redux but somebody didn't recommend it with litElemnt. One of the ideas I'm thinking is to dispatch the event to document instead of the current component, but maybe this is a bad practice! What are your suggestions, please let me know?


Solution

  • you have probably resolve this issue, but I can send you how I would deal with this.

    You need to lift your state up to your my-app component. This state will be a single source of truth and deliver count value to my-form and my-view child components.

    In my-app component, you can change handleCountChange to something like this (in case, if you define count as an attribute and let my-app to receive initial value from attributes):

     private handleCountChange(e: CustomEvent<{ count: number }>) {
       this.setAttribute('count', count); // If you set `count` as observed attribute, you will not need to call `requestUpdate` method
     }
    
    

    or like this, if you define count as class property

     private handleCountChange(e: CustomEvent<{ count: number }>) {
       this.count = count;
       this.requestUpdate(); // force to call render method which is necessary in this case
     }
    
    

    Note that, you will have to send count value also to the my-form component. It will work also without it, but if you will do it, you will lost single source of truth of your state and that can cause potential unexpected behaviour.

    If you will need to send an example, please let me know.