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

Error: Unable to access 'push' property of null object in Next.js Link

Utilizing Vite to develop a reusable component has led to an error upon publishing and reusing it: TypeError: Cannot read properties of null (reading 'push') The code for the component is as follows: import React from "react"; import ...

Understanding the separation and communication techniques in Vue.js components

I recently developed a small vuejs application and I find myself getting confused with the functioning of components. Here is the code snippet that I have been working on: <div id="app"> <div v-if="loggedIn"> <nav> <r ...

Tips for correctly linking JS and CSS resources in Node.js/Express

I have a JavaScript file and a stylesheet that I am trying to link in order to use a cipher website that I created. Here is my File Path: website/ (contains app.js/html files and package json) website/public/css (contains CSS files) website/public/scri ...

Updating the content of a Telerik RadEditor using Javascript/jQuery

I am currently facing a challenge in manually cleaning the HTML of a Telerik RadEditor using Javascript. Despite my efforts, I am struggling to find the appropriate location to store the value in order for it to be saved during post back. Below is the Jav ...

Using a data loader with react-router

I am currently working on a react app where I have implemented routes using the new data loaders from react-router-dom import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'; import Home fr ...

The functionality of JQuery's `.on("click"... is sporadically functioning

I have a code block that dynamically generates elements and includes event handling. However, the event handling sometimes works and other times it doesn't. I'm not sure how to debug this issue. Can someone help me figure out what might be causin ...

React and Material UI: Ensuring Proper Whitespace Handling in Strings

Exploring the use of Typography component from Material UI (https://material-ui.com/api/typography/) The main objective is to maintain the new lines and spaces in a saved string. For instance, if the string contains leading spaces and new lines, it shoul ...

The concept of IFA on Android and accessing it through the browser/javascript

IFA (Identifier for Advertisers) is a new feature in iOS 6 that functions as a unique ID for tracking user context anonymously and improving the overall experience. My inquiries mainly focus on the realm of web development (specifically mobile browser/jav ...

Sentry: Easily upload source maps from a nested directory structure

I am currently developing a NextJs/ReactJs application using Typescript and I am facing an issue with uploading sourcemaps to Sentry artefacts. Unlike traditional builds, the output folder structure of this app mirrors the NextJs pages structure, creating ...

The custom validation process encountered an error: callback is not a valid function

Encountering an issue with a custom validator in node.js while using mongoose. The goal is to verify if a query already exists in the headerLog before attempting to insert it. Take a look at the code snippet below: var mongoose = require('mongoose& ...

Endless Scroll Feature in Javascript

A brief tale: I am in search of a way to extract data from a website that features infinite scroll without actually implementing the infinite scroll feature myself. My plan is to create a script that will auto-scroll the page from the browser console, wai ...

Utilizing Unix timestamps for x-values while displaying dates as x-labels in an ECharts line chart

I'm struggling with incorporating date-converted unix timestamps as values in my ECharts graph. The x-axis of my chart is meant to display the recording time for each buy or sell price, represented by respective lines. Everything functions properly wh ...

Is there a way to retrieve the redirected URL from an AJAX request?

Hi everyone, I'm currently trying to solve a puzzle regarding how to identify whether a PHP script redirects during an AJAX call. Does anyone have insight into whether JQuery AJAX has the capability to detect and keep track of location changes? Let&a ...

What is the best method for sending a JavaScript variable to the server and back again?

I'm currently working on a JavaScript project where I need to build a string. Let's say, for the sake of this example: var cereal = 'my awesome string'; There's also a button on my webpage: <button id="send" type="submit" nam ...

Implementing a Vue.js v-bind:style attribute onto a dynamically generated element post-page initialization

Let me start by explaining my current issue and dilemma: I have been tasked with converting an existing JS project into a Vue.js framework. While I could easily solve a particular problem using jQuery, it seems to be posing quite a challenge when it comes ...

Looking to utilize vue.js to alter the color of the <li> element when a select option is chosen

I'm a beginner in vue.js and I'm attempting to change the background color by using the select option. Despite trying the cueCardsColor method, nothing seems to be happening. <ul> <li :class="+ cueCardColor"> <sele ...

Resolving the "Abstract type N must be an Object type at runtime" error in GraphQL Server Union Types

Given a unique GraphQL union return type: union GetUserProfileOrDatabaseInfo = UserProfile | DatabaseInfo meant to be returned by a specific resolver: type Query { getUserData: GetUserProfileOrDatabaseInfo! } I am encountering warnings and errors rel ...

What is the best way to customize the spacing of grid lines in chartist.js?

I am struggling with chartist.js. I want to increase the spacing between y-axis gridlines by 40px. (Currently set at 36px) I have tried looking for examples, but haven't found any. .ct-grids line { stroke: #fff; opacity: .05; stroke-dasharray: ...

Troubleshooting problem: Unable to restrict table selections - issue with Material UI table in React

I seem to be overlooking the simple solution here. Currently, I have a ternary on my table. If the length of the selected array is greater than a certain number, a table with disabled checkboxes is rendered. I also implement a different handleClick functio ...

Struggling to make JavaScript read JSON data from an HTML file

I am currently working on developing a word search game using django. One of the tasks I need to accomplish is checking whether the entered word exists in a dictionary. To achieve this, I have been converting a python dictionary into JSON format with json. ...