Skip to content

It's not possible to have nested stateful LiveVue components inside a LiveVue parent component #14

Open
@vheathen

Description

@vheathen

Seems that currently it's not possible to have nested LiveVue components added via slots

Here is an example:

  def render(assigns) do
    ~H"""
    <div class="w-[350px] flex flex-col text-center h-full justify-center gap-2">
      <div>
        <%!-- Top level component --%>
        <.Card class="m-5">
          <div class="p-10 flex flex-col text-center h-full justify-center gap-2">

            <p id="with-socket">
              <%!-- Nested component with socket, goes away after mount --%>
              <.Counter count={@count} v-socket={@socket} id="inner-with-socket" v-on:inc={JS.push("inc_counter")} />
            </p>

            <p id="without-socket">
              <%!-- Nested component without socket, shown, but static, doesn't have even a local state --%>
              <.Counter count={@count} id="inner-without-socket" v-on:inc={JS.push("inc_counter")} />
            </p>

          </div>
        </.Card>
      </div>

      <%!-- Another top level component --%>
      <div>
        <.Counter count={@count} v-socket={@socket} id="outer-with-socket" v-on:inc={JS.push("inc_counter")}  />
      </div>
    </div>
    """
  end

Card is a very simple Vue component which has only a slot inside:

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div :class="cn('rounded-xl border bg-card text-card-foreground shadow', props.class)">
    <slot />
  </div>
</template>

The very first Counter.vue disappears (this is slightly visually modified module included to LiveVue) disappears after the view mounted to the server.

The second one is there, but it doesn't have any state or behaviour - event the slider itself doesn't change the button label.

The third one - which is outside of the Card - is working as intended.

image

Funny enough that the top visible Counter gets one update when the server state changes the first time (the first "Increase counter" click) but then it stops.

I tried to look into the code but haven't found a good way to implement the case above.

Also, I noticed that each component gets its own application instance. I wonder if it makes sense to have only one app? In this case components can share state. Or it can be configurable. Don't think it worth spending time on that now TBH, but in the future can be interesting.

But the question is how should nested Vue components behave is an interesting topic. Currently to change server state from the nested Vue components (I mean, actual Vue components without anything in-between) it is necessary to emit events from the bottom to the top level one, which is actually got a VueHook, and then this component will emit an event to the LiveView controller.

May be an option here is to implement a server-side reactive store of some kind? It is possible to integrate pinia, but it will be too heavy only for that purpose, I think.

Anyway, I believe this topic worth another thread :-D

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions