ionic-team / stencil

A toolchain for building scalable, enterprise-ready component systems on top of TypeScript and Web Component standards. Stencil components can be distributed natively to React, Angular, Vue, and traditional web developers from a single, framework-agnostic codebase.
https://stenciljs.com
Other
12.41k stars 777 forks source link

prop changes not updating as I would expect #1818

Closed vidarc closed 2 years ago

vidarc commented 4 years ago

Stencil version:

 @stencil/core@1.2.5

I'm submitting a:

[x] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

Current behavior: @Prop() value does not change if updated quickly afterward. Example I am using is a checkbox that when on the change event, checks how many are checked, and if a certain amount are checked already, sets current back to false.

The watch function only gets triggered on the initial prop change, while it does not happen when the prop should be changed back to false. Not sure where the value is getting lost along the way. The below Vue code updates it's state correctly.

Using mutable and/or reflect produces the same result.

Expected behavior: Expect the above to allow for the checkbox to remain unchecked

Steps to reproduce:

Related code: stencil code

import { Component, Prop, Host, h, Event, EventEmitter, Watch } from '@stencil/core'

@Component({
  tag: 'custom-checkbox',
  shadow: true,
})
export class CustomCheckbox {
  checkbox: HTMLInputElement | undefined

  @Prop() checked: boolean

  @Watch('checked')
  onCheckedChange(newValue: boolean, oldValue: boolean) {
    console.log(newValue, oldValue)
  }

  @Event() customchange: EventEmitter<boolean>

  handleChange = () => {
    this.checked = !this.checked
    this.customchange.emit(this.checked)
  }

  render() {
    console.log('rerendering')
    return (
      <Host>
        <input type='checkbox' ref={element => this.checkbox = element} checked={this.checked} onChange={this.handleChange} />
      </Host>
    )
  }
}

vue code using above

  <div id="app">
    <template v-for="item in array">
      <custom-checkbox v-on:customchange="handleChange($event, item)" :checked="item.checked"></custom-checkbox>
    </template>
  </div>
new Vue({
      el: '#app',
      data: {
        array: [
          {
            id: 1,
            value: 'matt',
            checked: false,
          },
          {
            id: 2,
            value: 'parker',
            checked: false,
          }, 
          {
            id: 3,
            value: 'roger',
            checked: false,
          }, 
          {
            id: 4,
            value: 'francine',
            checked: false,
          }
        ]
      },
      methods: {
        handleChange(event, item) {
          item.checked = event.detail
          const filtered = this.array.filter(item => item.checked === true)

          if (filtered.length > 2) {
            item.checked = false
          }
        }
      }
    })

Above example is within Vue, but would expect the custom element to function correctly to prop changes regardless of framework it's within. Maybe I'm missing something simple though ¯\_(ツ)_/¯

johnstrickler commented 4 years ago

I had a similar issue with functional components where...

I worked around it by grabbing a reference to the element that the functional component creates and updating the element's value directly.

@Component({
  tag: 'my-input',
  shadow: true
})
export class MyInput {

  input: HTMLInputElement; // reference to the internal

  @State() formattedValue: string;

  @Listen('input', { capture: true })
  onInput(event: any) {

    // this does not update the functional component's value as expect
    this.formattedValue = this.format(this.input.value); // formatted value displayed by internal input

    // workaround - i have to grab a reference to the element that the functional component creates and set the value directly :(
    this.input.value = this.formattedValue;
  }

  render() {
    return (
        <FunctionalInput
          ref={el => this.input = el} 
          value={this.formattedValue}
        />
    );
  }
}

const FunctionalInput = (
  { value, ref },
  children
) => {

  return[
    <input
      value={value}
      ref={ref}
    />
    children
  ];
};
dgibson666 commented 4 years ago

I have a similar case where an app is updating multiple props in succession and they are all not being caught by the component. I chalked it up to a race condition because it's likely not done re-rendering the component when the next prop update is done. This can probably be worked around on our end, but if there's a way to handle this on Stencil's end, that would be great too.

stanley85 commented 4 years ago

In response to the first example, one could add two properties to the checkbox and do the check inside the checkbox itself.


@Element() element: HTMLElement;
@Prop() checked: string = false;
@Prop() group: string = 'default';
@Prop() limit: number = 2;

handleChange() {
        let checkedCount = 0;   
        this.element.parentNode.querySelectorAll('custom-checkbox').forEach((element) => {
            if (element.checked && element.group === this.group) {
                checkedCount++;
            }
        });
        if (checkedCount <= this.limit) {
            this.checked = !this.checked;
        }
    }
mnemanja commented 4 years ago

Any progress on this? I'm experiencing the same thing. Trying to update the parent component's state from child component by the means of callback past to the child props.

It works when, in the parent component, I wrap the state update in setTimeout. This is clearly a hack, but I see no other way of getting on the component-update-train of the Stencil-React component. The React library has this nicely handled. I wonder if there is something I'm missing or it just needs fixing?

splitinfinities commented 2 years ago

Hey there, thank you for the patience getting back to you. The new team is getting started and we're working through the backlog now.

We do not support Stencil versions less than v2, and this seems to be using an older version of Stencil. If you can upgrade to the latest version and let me know if this issue still exists, I would appreciate it.

Additionally, if someone could create a Reproduction repo summarizing all the use cases listed in this issue, that would help us identify if there is a problem and what the fix may be.

ionitron-bot[bot] commented 2 years ago

Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Stencil, please create a new issue and ensure the template is fully filled out.

Thank you for using Stencil!