Rendering illuminated component with continuous asynchronous updates

My task involves displaying a list of items using lit components. Each item in the list consists of a known name and an asynchronously fetched value.

Situation Overview:

  • A generic component named simple-list is required to render any pairs of name and value
  • There is also a specific configuration component called foo-list which passes data to the simple-list component for rendering
  • The time taken to fetch each value can vary, and their responses arrive at different times
  • Upon initial loading of the foo-list component, all names should display with their corresponding value set as "Loading..."
  • As each value response is received, the relevant text in the list should update to show the retrieved value.

I am facing challenges in getting the component to re-render for each update. Below is my code for both the simple-list and foo-list components.

  • The simple-list component accepts a data object and re-renders its list each time a new object is provided.
  • The foo-list component initially sends all names with temporary values to simple-list, retrieves values for each name, and updates the data object every time a value is obtained.
  • I suspect that calling this.requestUpdate within an async anonymous method might not work, but I'm unsure how best to structure this scenario.
  • If there's a more efficient way to pass data or instructions into the simple-list component, I would appreciate any suggestions.

How can I ensure that getDataAndUpdate displays both the initial value and the updated values once the promises are resolved?

// Modified-Simple-List.ts

import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

export type SimpleListItem = {  name: string, value: string };

@customElement("simple-list")
export class SimpleList extends LitElement {
  @property({ type: Array, attribute: false })
  items: SimpleListItem[] = [];
  update(changedProperties: Map<string, unknown>) {
    super.update(changedProperties);
    if (changedProperties.has("items")) {
      this.requestUpdate();
    }
  }
  render() {
    return html`<ul>${this.items.map((item) => html`<li>${item.name}: ${item.value}</li>`)}</ul>`;
  }
}
// Custom-Foo-List.ts

import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

import "./Modified-Simple-List";
import { SimpleListItem } from "./Modified-Simple-List";

async function getValue(name: string): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Value for ${name}`);
    }, Math.random() * 1000);
  });
}

@customElement("foo-list")
export class MyFoo extends LitElement {
  @property({ type: Array })
  names: string[] = [];

  @property({ type: Array, attribute: false })
  data: SimpleListItem[] = [];

  constructor() {
    super();
    this.getDataAndUpdate();
  }

  update(changedProperties: Map<string, unknown>) {
    super.update(changedProperties);
    if (changedProperties.has("names")) {
      this.getDataAndUpdate();
    }
  }

  async getDataAndUpdate() {
    this.data = [];
    for (let i = 0; i < this.names.length; i++) {
      const value = "loading...";
      const name = this.names[i];
      (async () => {
        this.data[i] = { name , value: await getValue(name) };
        this.requestUpdate();
      })();
      this.data.push({ name, value });
    }
    this.requestUpdate();
  }

  render() {
    return html` <simple-list .items=${this.data}></simple-list> `;
  }
}

Answer №1

Solution 1

To simplify the process, consider allowing the <simple-list> component to accept promises as values and utilize the until() directive.

// Simple-List.ts

import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js";

export type SimpleListItem = { name: string, value: Promise<string> };

@customElement("simple-list")
export class SimpleList extends LitElement {
  @property({ type: Array, attribute: false })
  items: SimpleListItem[] = [];

  render() {
    return html`<ul>${this.items.map((item) => html`<li>${item.name}: ${until(item.value, "Loading...")}</li>`)}</ul>`;
  }
}
// Foo-List.ts

import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

import "./Simple-List";
import { SimpleListItem } from "./Simple-List";

async function getValue(name: string): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Value for ${name}`);
    }, Math.random() * 1000);
  });
}

@customElement("foo-list")
export class MyFoo extends LitElement {
  @property({ type: Array })
  names: string[] = [];

  render() {
    const items = this.names.map((name) => ({ name, value: getValue(name)}));
    return html`<simple-list .items=${items}></simple-list>`;
  }
}

View a live example here:

Solution 2

If <simple-list> should only handle primitive data, then <foo-list> must update the props accordingly. Use the willUpdate() lifecycle method to manage state synchronization with placeholders. The resolution of each promise will trigger updates for both <foo-list> and <simple-list> that receives it.

// Simple-List.ts

import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

export type SimpleListItem = { name: string, value: string };

@customElement("simple-list")
export class SimpleList extends LitElement {
  @property({ type: Array, attribute: false })
  items: SimpleListItem[] = [];

  render() {
    return html`<ul>${this.items.map((item) => html`<li>${item.name}: ${item.value}</li>`)}</ul>`;
  }
}
// Foo-List.ts

import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";

import "./Simple-List";
import { SimpleListItem } from "./Simple-List";

async function getValue(name: string): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Value for ${name}`);
    }, Math.random() * 1000);
  });
}

@customElement("foo-list")
export class MyFoo extends LitElement {
  @property({ type: Array })
  names: string[] = [];

  @state()
  _data: SimpleListItem[] = [];
  
  willUpdate(changedProperties: PropertyValues<this>) {
    if (changedProperties.has('names')) {
      this._data = this.names.map((name) => {
        getValue(name).then((value) => {
          // Update state immutably after data retrieval
          this._data = this._data.map((item) => {
            if (item.name === name) {
              return {
                name: item.name,
                value,
              }
            }
            return item;
          });
        });
        return {
          name,
          value: 'Loading...',
        }
      });
    }
  }

  render() {
    return html`<simple-list .items=${this._data}></simple-list>`;
  }
}

See it in action here:

Solution 3

Another approach involves caching results within the component and using this.requestUpdate() upon promise resolution instead.

// Foo-List.ts

import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";

import "./simple-list.js";
import { SimpleListItem } from "./simple-list.js";

async function getValue(name: string): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Value for ${name}`);
    }, Math.random() * 1000);
  });
}

@customElement("foo-list")
export class MyFoo extends LitElement {
  @property({ type: Array })
  names: string[] = [];
  
  private _values = new Map<string, string>();
  private _getValue(name: string): string {
    const value = this._values.get(name);
    if (value === undefined) {
      getValue(name).then(val => {
        this._values.set(name, val);
        this.requestUpdate();
      });
    }
    return value ?? 'Loading...'
  }

  render() {
    const items = this.names.map((name) => ({
      name,
      value: this._getValue(name)
    }))
    return html`<simple-list .items=${items}></simple-list>`;
  }
}

Experience it in action:

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide to adding a line break following each set of 200 characters utilizing jQuery

I have a text input field where I need to enter some data. My requirement is to automatically add a line break after every 200 characters using JavaScript or jQuery. For example: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ...

Creating Highcharts series with dynamic addition of minimum and maximum values

I am currently working with a Highcharts chart that includes multiple tabs allowing users to display different data on the graph. I am dynamically adding series for each of these data points, which has been functioning well. However, I have encountered an ...

Unique Custom Resources like CSS and JavaScript are used to ensure a consistent appearance across all applications

We have identified a challenge where we aim to develop custom CSS, Javascripts and other resources to maintain consistent look and feel across all our applications. These applications could be built using GWT, Thingworx, JSP, etc., and may differ in natur ...

Guide to showcasing object characteristics inside an object in Angular2

My USAFacts object contains properties like StateName, as well as objects like State-Bird which hold information about the state bird. If written in JSON, a record of USAFacts would appear as follows: {"StateName": "PA", "State-Bird": [ { "Name": "Ruffed ...

The callback function fails to execute the click event following the .load() method

Hey there, I've hit a roadblock and could really use some help figuring out where I went wrong. Let me break down my script for you. On page1.html, I have a div that gets replaced by another div from page2.html using jQuery's .load() method. Here ...

Unable to retrieve information from req.body, however able to retrieve data from req.params

I'm facing an issue with my code where I am not able to retrieve user data from req.body as it returns undefined. However, I can successfully access the required information from req.params. Can anyone provide guidance on how to resolve this issue? co ...

What is the best approach to incorporate Column Reordering in react-data-grid?

Within my REACT application, I have incorporated the npm package react-data-grid. They offer a sample showcasing Column Reordering on their website. I wish to replicate this functionality in my own code. Upon reviewing their source code, I am puzzled abou ...

Tips for eliminating the trailing slash from the end of a website article's URL

I've recently delved into learning Gatsby, and I've encountered an issue with the Open Graph tag in my project. The og:image is displaying a different image than the intended thumbnail for the article. Here's an example article - . When try ...

Navigate within the div by scrolling in increments of 100%

I am facing an issue with a div that contains multiple children set to 100% height. My goal is to scroll exactly the height of one child (which is also 100%) on each scroll. However, I am struggling to prevent scrolling multiple steps at a time. I have tri ...

Adjust the position of vertices when the mouse hovers over them

I have a PlaneGeometry and I am trying to adjust the z position of the vertex being hovered over, but I am uncertain about how to retrieve it. //THREE.WebGLRenderer 69 // Creating plane var geometryPlane = new THREE.PlaneGeometry( 100, 100, 20, 10 ); ...

Using jQuery to include a sub-object in the "data" object for each AJAX request made on the webpage

Is there a way to enhance the functionality of jQuery.ajax by including a static sub-data object in every ajax request automatically? For instance, for an ajax request like this: jQuery.ajax({ url: 'request_file.php', data: { da ...

Ways to determine the position of elements when they are centered using `margin:auto`

Is there a more efficient way to determine the position of an element that is centered using CSS margin:auto? Take a look at this fiddle: https://jsfiddle.net/vaxobasilidze/jhyfgusn/1/ If you click on the element with the red border, it should alert you ...

The Protractor option is nowhere to be found on the Run Configuration popup window in Eclipse

Issue with Protractor in Eclipse: Unable to locate Protractor option on Run Configuration popup. Despite following the steps outlined in http://www.protractortest.org/#/ and this guide on configuring Protractor with Eclipse (specifically the 2 Answer step ...

The Heroku Node.js application encountered an issue when trying to apply the style due to an incompatible MIME

As a complete beginner in Node.js and Express, I am encountering some errors from the console. When trying to load my CSS file from '', I receive the following error: "Refused to apply style because its MIME type ('text/html') i ...

Refresh the content of an iframe (local HTML) by clicking on buttons

When a user clicks on a button, I am loading an HTML file into a DIV (iframe). The issue arises when the user clicks another button and wants to reload the iframe with a different HTML file. This is the current code snippet: <script type="text/javasc ...

Leveraging Vue js components while utilizing it from a content delivery network (CDN

I'm attempting to utilize the ButtonCounter component as a demonstration (source: https://vuejs.org/guide/essentials/component-basics.html#defining-a-component), but I am facing difficulties in getting it to function properly. I am utilizing Vue.js 3 ...

Creating a table using Ng-repeat in AngularJS: A Step-by-Step Guide

I'm trying to figure out how to create the table below using ng-repeat. Unfortunately, I don't have permission to modify the json structure so I need to work with it as is. Here's my json: $scope.carCollection = { 'Toyota': [ ...

Error TS2345: The argument provided, which is of type 'Promise<ReadonlyArray<Object>>', cannot be assigned to a parameter that must be of type 'T | PromiseLike<T> | undefined'

My goal is to return the ReadonlyArray<> in my promise so that I can send it back to the original method that called 'dispatchToThisProcess'. This abstraction allows for potential future updates to multiple processes. Below is the code snip ...

Dynamically insert a new row into an HTML table using AJAX and refresh the table with .load method

I am facing an issue with my HTML table that loads data dynamically through a PHP snippet containing SQL queries. There is a Select option and a button on the table to add a new row, which triggers an AJAX procedure to send the data to PHP for insertion in ...

Is there a way to modify my code to eliminate the need for a script for each individual record?

Whenever I need to create a code with the ID by browsing through my records, is there a way to make just one function for all the records? $tbody .= '<script> $(document).ready(function(){ $("#img'.$idImage .'").click(functi ...