February 21, 2023

Creating Dynamic and Reactive Components with StencilJS State and Props when passing an Array

In web development, state refers to the data that is used to render the content of a web page or component. In StencilJS, state is an important concept that enables you to define and manage data within your components. By using state, you can create dynamic and reactive web components that respond to changes in user input or other data.

In this blog post, we’ll explore how to use state in StencilJS to refresh and render the template of a component when a prop that is expecting an array type is passed. We’ll start by explaining what state is and how it works in StencilJS, and then move on to a practical example that demonstrates how to use state to create a dynamic and reactive component.

What is State in StencilJS?

State is a built-in feature of StencilJS that enables you to define and manage data within your components. State can be used to represent any type of data, including strings, numbers, arrays, objects, and more. When you define a state property in a StencilJS component, the framework automatically creates a getter and setter function that you can use to retrieve or update the value of the state property.

State is particularly useful when you want to create reactive components that respond to changes in user input or other data. By updating the value of a state property, StencilJS will automatically re-render the component’s template to reflect the new data. This can help to create dynamic and responsive user interfaces that are more engaging and interactive.

Using State to Render a Dynamic Component Template

To demonstrate how to use state in StencilJS to refresh and render the template of a component when a prop that is expecting an array type is passed, we’ll create a simple component that displays a list of items. We’ll start by defining a state property that represents the list of items that we want to display:

import { Component, h, Prop, State, Watch } from '@stencil/core';

@Component({
  tag: 'item-list',
})
export class ItemList {
  @Prop({ mutable: true }) data: string[] = [];
  @State() items: string[] = this.data;

  @Watch('data')
  dataChanged(newValue: string[]) {
    this.items = [...newValue];
  }

  render() {
    return (
      <div>
        <ul>
          {this.items.map((item) => (
            <li>{item}</li>
          ))}
        </ul>
      </div>
    );
  }
}

In this example, we have added a @Watch() decorator to the data prop. The @Watch() decorator listens for changes to the data prop and triggers the dataChanged() method, which updates the value of the items state property with the new value of the data prop.

We have also set the mutable option of the @Prop() decorator to true. This allows the data prop to be updated externally, which will trigger the @Watch() decorator and re-render the component.

By using the @Watch() decorator in combination with StencilJS state, we can create dynamic and reactive components that respond to changes in external data.

To test it, we can use our component and set an interval to update the array we pass to our prop every 3 seconds.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My StencilJS App</title>
    <link rel="stylesheet" href="build/global-styles.css" />
  </head>
  <body>
    <item-list id="my-list"></item-list>
    <script>
        var alist = document.querySelector('#my-list');
        var data = [];
        var counter = 0;
        
        setInterval(() => {
        data.push(++counter);
        alist.data = [...data];
        }, 3000);
    </script>
  </body>
</html>

When the data prop is changed externally, the dataChanged() method is called, which updates the items state property and triggers a re-render of the component. This results in the list being updated with the new data.

Noticed that both in the component declaration and inside the interval we are using the spread operator to copy the array values to a new array. This is because the component keeps a reference to the Array in memory and not whether the data in the array has changed.

You can get away with doing the copy when you assign the data prop externally but you will run into issues if the update is coming internally from a nested child component event through the Listen decorator. In this example, if we do not pass in a new array externally Watch will not fire after the first assignment and we will not be able to update the state and trigger the re-render.