BuilderIO/mitosis

Custom event compile incorrect in child component

kingzez opened this issue · 6 comments

I am interested in helping provide a fix!

Yes,need guide

Which generators are impacted?

  • All
  • Angular
  • HTML
  • Preact
  • Qwik
  • React
  • React-Native
  • Solid
  • Stencil
  • Svelte
  • Vue
  • Web components

Reproduction case

https://mitosis.builder.io/?outputTab=G4VwpkA%3D&code=JYWwDg9gTgLgBAbzgVwM4FMDKNrrgXzgDMoIQ4ByAAQCNlgAbAE3SgDpgIB6EYHVYKgoAoUJFiI4ASQB2YZPACGqOAAVFs%2BfEIkylNmy7A5CkcPQAPcfBZFFyBvCLIZAYxicZcbOjBhWABRgpGCoAJSIwnBwrhAyqPAJijB4ALwoGNi4AQhR0XAAFooyTAzoAEIMyFAB6ABu6DIwAFxwAGIQrmgAog1NEbn5BAA0wnnRRSVlAMKTAObotX0tcAlQxnMD4%2Fmx8RBlbAwQcwEUrvPoFMNw9Y0wYdv4o9H4D3lQ6DDVXgHbADzqTQKbbROIdLqoVIIJZ3CKpAB8cGCEFCbDBnTQMP6%2BBBcDis2KCyhWPucARqxgyXQbEmpXQBJkCxJYRxQ1BMkq1WJt36ZMRSRSNOKdM5NR591Z%2BS48LG0QeOLGXAAVEqokq1ED4II4ABZPgQAQqWaMJhwaZkSAyO5qvEycFoa74i7XADyHKqUDg2ohOHI4ptNtkcAAash0NcfXpxXBlEiQiowMoVDgYha4nc4DIIPAqOKIwUTanwOmmqsChAHKbFAxUBAi2BGHgU8jQgG4H9EwBaYxabboiGpABE%2FcxABJxWFB32ZAyiYPaTMLgFx8tJ9PRUOFxUPcuJ1PovC%2Flwuz2FDK8kHswVWMQoIoQOgAO7QADWmezqagH3cNwsrl88CDgASugig%2Fq495gPuSpcEAA%3D%3D%3D

Expected Behaviour

import { useStore } from '@builder.io/mitosis'
import { Input as PaInput } from '../input'

export default function Stepper(props) {
  const state = useStore({
    handleBlur(event: FocusEvent) {
    },

    handleChange(event: string) {
      console.log('change', event)
    },
  })

  return (
    <PaInput
      onFocus={(event) => props.onFocus(event)}
      onChange={(event) => state.handleChange(event)}
      onBlur={(event) => state.handleBlur(event)}
    />

  )
}

PInput is Mitosis Child Component, onFocus, onChange, onBlur is custom event,

  • In Vue, custom event as props pass to component not @event, child component should also compile to props,
  • In other framework not correct except "React camp"
<pa-input
  onFocus="onFocus($event)"
  onChange="handleChange($event)"
  onBlur="handleBlur($event)"
></pa-input>

Actual Behaviour

Compile it into the Custom Event writing method of the corresponding framework, not the props writing method

Additional Information

No response

This would be a duplicate of #833. There's a bit of a long discussion around it, and I need to find the time to dive deeper into what a solution would look like.

Yes, they are somewhat similar, but they can also be grouped together. I'm a little curious about whether there is such a usage scenario in the builder sdk, using a Child component in a Component.

Haven't had an issue with the lack of Vue's events feature in Mitosis. It seems like just syntactic sugar/a nice-to-have feature, there's nothing wrong with passing onFocus events to Vue. It's "react-style" but nothing breaks IMO.

@samijaber

No No No, this is not just syntax sugar. This problem prevents us from reusing existing components in mitosis new components, which is a fatal bug. You may not understand the example above, the focus is on the compilation of custom events in child components, not the syntax sugar in #833.

Now using mitosis compiled basic components

Basic component Input

mitosis code

// input.lite.tsx
import { useStore } from '@builder.io/mitosis'

export default function Input(props) {
  const state = useStore({
    handleFocus(event) {
      props.onFocus && props.onFocus(event.target?.value)
    },

    handleChange(event) {
      props.onChange && props.onChange(event.target?.value)
    },

    handleBlur(event) {
      props.onBlur && props.onBlur(event.target?.value)
    },
  })

  return (
    <input
      onFocus={(event) => state.handleFocus(event)}
      onChange={(event) => state.handleChange(event)}
      onBlur={(event) => state.handleBlur(event)}
    />
  )
}

compiled vue

<!-- input.vue -->
<template>
  <input @focus="handleFocus($event)" @input="handleChange($event)" @blur="handleBlur($event)" />
</template>

<script>
  import { defineComponent } from 'vue'

  export default defineComponent({
    name: 'pa-input',

    props: ['onFocus', 'onChange', 'onBlur'],

    methods: {
      handleFocus(event) {
        this.onFocus && this.onFocus(event.target?.value)
      },
      handleChange(event) {
        this.onChange && this.onChange(event.target?.value)
      },
      handleBlur(event) {
        this.onBlur && this.onBlur(event.target?.value)
      },
    },
  })
</script>

Usage scenario one

This is how we use the Input component in a vue project. onFocus, onChange, and onBlur are 'react-style' events passed into the component as props, which is no problem.

<!-- Home.vue -->
<template>
  <pa-input
    :onFocus="onFocus($event)"
    :onChange="handleChange($event)"
    :onBlur="handleBlur($event)"
  ></pa-input>
</template>

<script>
  import { defineComponent } from 'vue'
  import { Input as PaInput } from './input.vue'

  export default defineComponent({
    components: { PaInput: PaInput },

    methods: {
      handleBlur(event) {},
      handleChange(event) {
        console.log('change', event)
      },
    },
  })
</script>

Usage scenario two

Use the basic component Input in a new component. In mitosis, a new component Stepper uses the Input component, reusing our own component. Similarly, onFocus, onChange, and onBlur events are passed into the component as props, because that's how they're defined in our child component Input. So far, there is no problem, and then we compile to react and vue.

mitosis code

// stepper.lite.tsx
import { useStore } from '@builder.io/mitosis'
import { Input as PaInput } from './input.lite'

export default function Stepper(props) {
  const state = useStore({
    handleStepperFocus(event) {},
    handleStepperChange(event) {},
    handleStepperBlur(event) {},
  })

  return (
    <PaInput
      onFocus={(event) => state.handleStepperFocus(event)}
      onChange={(event) => state.handleStepperChange(event)}
      onBlur={(event) => state.handleStepperBlur(event)}
    />
  )
}

compiled react

// stepper.tsx
import * as React from 'react'
import { Input as PaInput } from './input'

function Stepper(props) {
  function handleStepperFocus(event) {}

  function handleStepperChange(event) {}

  function handleStepperBlur(event) {}

  return (
    <PaInput
      onFocus={(event) => handleStepperFocus(event)}
      onChange={(event) => handleStepperChange(event)}
      onBlur={(event) => handleStepperBlur(event)}
    />
  )
}

export default Stepper

compiled vue

<!-- stepper.vue -->
<template>
  <pa-input
    @focus="handleStepperFocus($event)"
    @change="handleStepperChange($event)"
    @blur="handleStepperBlur($event)"
  ></pa-input>
</template>

<script>
  import { defineComponent } from 'vue'

  import { Input as PaInput } from './input.vue'

  export default defineComponent({
    name: 'stepper',
    components: { PaInput: PaInput },

    methods: {
      handleStepperFocus(event) {},
      handleStepperChange(event) {},
      handleStepperBlur(event) {},
    },
  })
</script>

The above is the compilation result of react and vue:

  • In react, onFocus, onChange, and onBlur events are passed into the component as props, which is no problem.
  • In vue, onFocus, onChange, and onBlur events should also be passed into the component as props, consistent with Usage scenario one, but in the actual compilation result, they become @focus, @change, and @blur, which is a problem.

The correct compilation result of vue should be as follows:

<!-- stepper.vue -->
<template>
  <pa-input
+    :onFocus="handleStepperFocus($event)"
+    :onChange="handleStepperChange($event)"
+    :onBlur="handleStepperBlur($event)"
-    @focus="handleStepperFocus($event)"
-    @change="handleStepperChange($event)"
-    @blur="handleStepperBlur($event)"
  ></pa-input>
</template>

<script>
  import { defineComponent } from 'vue'

  import { Input as PaInput } from './input.vue'

  export default defineComponent({
    name: 'stepper',
    components: { PaInput: PaInput },

    methods: {
      handleStepperFocus(event) {},
      handleStepperChange(event) {},
      handleStepperBlur(event) {},
    },
  })
</script>

This could be the cause of the problem:

https://github.com/BuilderIO/mitosis/blob/main/packages/core/src/generators/vue/blocks.ts#L320-L350

Add condition only VALID_HTML_TAGS.includes(node.name) meet this, and other target should also add this.

if (key.startsWith('on') && VALID_HTML_TAGS.includes(node.name)) {

@samijaber Please take a good look at this. Am I right or wrong?