When working with lit.dev templates, very occasionally I find myself needing a reference to DOM elements that are being created inside a loop. Typically, this will be when I need to run animations on them and need a reference to them to do so.

Suppose our render function looks like this:

render() {
    return html`
        ${this.speakers.map(
            (speaker: any) => html`
                <div class="wht-Title">
                    <p class="wht-Speaker">${speaker.name}</p>
                    <p class="wht-Context">${speaker.context}</p>
                </div>
            `
        )}
    `;
}

And I want to have a reference to each of the wht-Title elements. There is no built-in way I know of to create a reference dynamically. However, this Stack Overflow answer for React based project was close enough that adapting it for lit.dev was a breeze.

As we loop over the items we can push them into an array that we later access. So we need our array, and a function to push the refs into it:

@property()
titleRefs: any = [];

setRef = (ref: any) => {
   // lit sometimes pushes in an undefined so return early if that happens
    if (ref === undefined) {
        return;
    }
    this.titleRefs.push(ref);
};

Yea, yea, my types are any, I don’t give two shits TypeScript, I’m just getting this thing working, capiche?

And then we adapt our loop to use that function to push a reference in:

render() {
    return html`
        ${this.speakers.map(
            (speaker: any) => html`
                <div ${ref(this.setRef)} class="wht-Title">
                    <p class="wht-Speaker">${speaker.name}</p>
                    <p class="wht-Context">${speaker.context}</p>
                </div>
            `
        )}
    `;
}

And now, by firstUpdated() we have those elements stored in our this.titleRefs property.

And we can access them like this using the index (idx) where needed to match up the appropriate element:

this.titleRefs.map((title: any, idx: number) => {
  title.animate(this.showTitle, {
    delay: this.speakers[idx].timestamp * 1000,
    duration: 300,
    fill: "forwards",
    easing: "ease-in-out",
  });
});