0
votes

I wanted to refactor the code from this tutorial about render functions to use Composition API. Everything is working fine except context.emit("update:activeTabId", tab.props.tabId); line in onClick function. Console logging the tab.props.tabId shows that onClick function works and properly read the tabId, but the update:activeTabId doesn't update the activeTabId value. Is there another way of emitting the event with sync modifier in Composition API or am I doing something wrong?

Here is my code:

---- App.vue ----

<template>
<!-- eslint-disable-next-line -->
  <tab-container v-model:activeTabId="activeTabId">
    <tab tabId="1">Tab #1</tab>
    <tab tabId="2">Tab #2</tab>
    <tab tabId="3">Tab #3</tab>

    <tab-content tabId="1">Content #1</tab-content>
    <tab-content tabId="2">Content #2</tab-content>
    <tab-content tabId="3">Content #3</tab-content>
  </tab-container>
</template>

<script>
import {ref} from 'vue'
import {Tab, TabContent, TabContainer} from './components/tabs.js';

export default {
  components: {
    Tab, 
    TabContent,
    TabContainer
  },
  setup() {
    const activeTabId = ref('1');

    return {
      activeTabId,
    };
  },
};
</script>

--- tabs.js ---

import { h } from "vue";

export const TabContainer = {
  props: {
    activeTabId: {
      type: String,
      required: true,
    },
  },
  emits: ['update:activeTabId'],
  setup(props, context) {
    const $slots = context.slots.default();
    const tabs = $slots
      .filter((x) => x.type === Tab)
      .map((tab) =>
        h(tab, {
          class: {
            tab: true,
            active: props.activeTabId === tab.props.tabId,
          },
          onClick: () => {
            console.log(tab.props.tabId)
            context.emit("update:activeTabId", tab.props.tabId);
          },
        })
      );

    const content = $slots.find(
      (slot) =>
        slot.props.tabId === props.activeTabId && slot.type === TabContent
    );

    return () => [
      h(() => h("div", { class: "tabs" }, tabs)), 
      h(() => h("div", content))
    ];
  },
};

const tabItem = (content) => ({
  ...content,
  props: {
    tabId: {
      type: String,
      required: true,
    },
  },
  setup(_, context) {
    return () => h("div", context.slots.default())
  }
});

export const Tab = tabItem({ name: "Tab" });
export const TabContent = tabItem({ name: "TabContent" });
1
I’ve did a quick test and your code seems to work, the parent model is correctly being updated for me: codesandbox.io/s/flamboyant-brook-46r62?file=/src/App.vue - LeBen
Thank you @LeBen for your response. Yes, value of the activeTabId is being updated in the App.vue, but somehow new value is not being passed to the TabContainer. It seems as TabContainer is rendered only once and never updates. On tab click Content value should change. - bukacz

1 Answers

0
votes

After some research, I've found that activeTabId is being updated but TabContainer somehow doesn't consider it reactive value so it wasn't changing. I've wrapped all TabContainer logic with watchEffect watching for activeTabid changes and it now works as it should!

https://codesandbox.io/s/solitary-currying-50ick?file=/src/App.vue