1

I want a parent component to manage a central state of the application and pass down the relevant information to its children, re-rendering them as needed. This is how I understood parent/child communication in Stencil - the parents send data to the children via properties and the children send data to the parent via events. Is this assumption wrong?

I am having trouble finding a relevant example online that does not reference React-specific API's

Edit: It may be relevant that, in my case, the parent is rendering a wrapper component with a slot. Code sample updated to reflect that...

Simplified Example:

parent.tsx:

// Parent Component
import { Component, State, Listen, h } from '@stencil/core';

@Component({
  tag: 'parent',
  styleUrl: 'parent.css',
})
export class Parent {
  @State() selectedFeature: any = null;

  @Listen('featureClicked', { target: 'body' })
  updateSelectedFeature(e: CustomEvent) {
    this.selectedFeature = e.detail;
  }

  render() {
    return [
      <wrapper>
        <child slot="content" selected-feature={this.selectedFeature}></child>
      </wrapper>,
      // ...other components
    ];
  }
}

child.tsx:

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

@Component({
  tag: 'child',
  styleUrl: 'child.css',
})
export class Child {
  @Prop() selectedFeature!: any;

  render() {
    if (!this.selectedFeature) return null;

    return (
      <ul>
        {
          Object.entries(this.selectedFeature.attributes)
            .map((key, val) => <li>{key}: {val}</li>)
        }
      </ul>
    );
  }
}

When a new feature is clicked, I want the child component to display it. Currently, I can't make that happen unless:

  • I pass a valid feature on the first render (in which it renders correctly once)
  • Have a listener within the child to catch the selected feature from the DOM

Is there a way to do this without the child listening for events?

Thanks in advance.

Ethan
  • 35
  • 6
  • 1
    The child component should automatically rerender when a property changes (but not if you set it to the same value). Are you perhaps updating it to the same reference? Check https://stenciljs.com/docs/reactive-data#updating-an-object – Thomas Feb 17 '22 at 14:21
  • @Thomas, I believe the reference changes every time a different feature is clicked. In fact, I'm certain that the parent component re-renders when `selectedFeature` is updated, but the children do not. – Ethan Feb 17 '22 at 15:16
  • One thing I noted: Stencil passes data via props by default, so instead of `selected-feature` you should use `selectedFeature`. Otherwise it will be passed as a string attribute. – Thomas Feb 18 '22 at 08:30
  • @Thomas, that's interesting and I didn't know that. The [docs](https://stenciljs.com/docs/properties#variable-casing) mention a preference to use camel-case props in tsx files, but do not specify that dash-case variables are interpreted as strings. Do you have supporting documentation? – Ethan Feb 18 '22 at 22:25
  • 1
    The camelCase version is the property, dash-case is an HTML attribute that Stencil automatically generates and [HTML attributes are always strings or booleans](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#content_versus_idl_attributes). You're right that the docs could be clearer on that. – Thomas Feb 23 '22 at 12:51

3 Answers3

1

The name of the property in the child component is selectedFeature but the parent component is passing its value as selected-feature, which is interpreted as a HTML-attribute and not a Stencil property. Hence, the property will never change and the child won't re-render.

Change the line accordingly and it should work:

<child slot="content" selectedFeature={this.selectedFeature}></child>

JSRookie
  • 26
  • 2
0

Your assumption is right. Pass down the state via props and emit events and have listeners to listen to these events.

Props are immutable within component but if you wish to make it mutable, stencil provides

 @Prop({ mutable: true }) propertyThatIsMutable;

Also check

@Prop({ reflect: true }) propToReflect; // will reflect as a attribute in DOM.
Parag Diwan
  • 159
  • 7
0

I found a workaround suitable for my needs, which is to use @stencil/store.

Docs: https://stenciljs.com/docs/stencil-store

Example: https://github.com/ionic-team/stencil-store/tree/master/test-app

Components that are dependent on this state will re-render on state change, which is nice.

Not marking this as solved, since it does not address the original problem.

Ethan
  • 35
  • 6
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 17 '22 at 21:27