vimeshjs / vimesh-ui

Vimesh UI is an ultra lightweight library to build UI components for Alpine.js
MIT License
125 stars 5 forks source link

[QUESTION] Component State #1

Open go4cas opened 1 year ago

go4cas commented 1 year ago

@xinjie-zhang, is the only way to define local state for each component using the inline x-data directive? I have a few components with more complex state, example: nested objects, arrays, etc. Is it possible to define this inside the <script> tag of the component? (similar to how init() is done)?

I have tried a few options (bit none working for me):

<template x-component="customer" x-data="customer" x-init="$api && $api.init()">
  <div x-text="customer.name"></div>
  <div x-text="customer.email"></div>
  <script>
    return {
      init() {
        this.customer = { name: 'John Smith', email: 'john@example.com' }
      },
      customer: {}
    }
  </script>
</template>
<template x-component="customer" x-data="$api && $api.data()" x-init="$api && $api.init()">
  <div x-text="customer.name"></div>
  <div x-text="customer.email"></div>
  <script>
    return {
      init() {
        this.customer = { name: 'John Smith', email: 'john@example.com' }
      },
      data() {
        return {
          customer: {}
        }
      }
    }
  </script>
</template>

Is there anyway to achieve this?

go4cas commented 1 year ago

I got this working by removing the x-data directive altogether:

<template x-component="customer" x-init="$api && $api.init()">
  <div x-text="customer.name"></div>
  <div x-text="customer.email"></div>
  <script>
    return {
      init() {
        this.customer = { name: 'John Smith', email: 'john@example.com' }
      },
      data() {
        return {
          customer: {}
        }
      }
    }
  </script>
</template>

Is this the preferred way?

xinjie-zhang commented 1 year ago

I'm working on headless UI (https://github.com/vimeshjs/vimesh-headless), which will implement Tailwind headless UI (https://headlessui.com/) with Alpine.js. There are already some components like listbox, switch, tabs etc. It is my preferred way to develop components.

Let me explain $api and x-data. $api is inherited from x-data. The difference is $api is not directly visible to the child elements. x-data is accessible to all children elements. So if you want properties and methods visible to its descendant, use x-data. If you want to make something only available to current element, use $api. Lets say x-data is "public", $api is "private". In some cases, you want to access invisible $api of its ancestor, you could use $api.$of(component name of ancestor). There are many examples in vimesh headless ui. For initialization, x-data use init(). In $api, you could use onMounted(), and onUnmounted() for destroy.

For your case, I would propose :

<template x-component="customer">
  <div x-text="$api.customer.name"></div>
  <div x-text="$api.customer.email"></div>
  <script>
    return {
      customer:{},
      onMounted() {
        this.customer = { name: 'John Smith', email: 'john@example.com' }
      }
    }
  </script>
</template>
go4cas commented 1 year ago

Great! Thanks for the detailed explanation, @xinjie-zhang!