vue.jsvue-composition-apivue-reactivityvue-options-api

Composable reactivity within a mix of options and composition APIs


"vue": "^2.7.16" "nuxt": "^2.18.1" "@nuxt/bridge": "^3.3.1"

I have a composable that is using Composition api.

import {
  ref,
  reactive,
} from 'vue';

export function useComponentSwitcher() {
  const views = {
    orderDetails: 'orderDetails',
    orderHistory: 'orderHistory',
  };

  const currentComponent = reactive({
    name: views.orderDetails, // initial view
  });

  const currentView = ref(currentComponent);

  function switchView(view) {
    currentView.value.name = view;
  }

  return {
    views,
    currentView,
    switchView,
  };
}

I'm importing the composable in my Vue component that is using Options api, that is adding it to setup().

import { useComponentSwitcher } from '@order-management/composables';

export default {
  setup() {
    const { views, currentView } = useComponentSwitcher();

    return {
      views,
      currentView,
    };
  },
}

Then in the component's template tag I'm checking the currentView name to rendering either child component

<template>
<ChildComponentA
     v-if="currentView.name === views.orderDetails"
/>
<ChildComponentB
     v-if="currentView.name === views.orderHistory"
/>
</template>

In the composable the watchEffect does log the correctly updated value however the v-if does not re-evaluate and seems to not be reacting to the change in data.

I got it working using a store however, but I'm not sure why it didn't work this way.

Could it be a mix of Options and Composition API?

I expect that the Child components A and B should dynamically switch on the UI when switchView gets called.

When I change the initial currentComponent name in the composable between the 2 views state, it does work as expected. Only doesn't work when calling composable.switchView()


Solution

  • Vue 2's reactivity system is based on Object.defineProperty, which has some limitations compared to Vue 3's Proxy-based system. This can cause issues when trying to make reactive objects work across different APIs.

    In your composable, you are using reactive and ref correctly, but when you return currentView from the setup function, it might not be reactive in the Options API context.

    watchEffect is a Composition API feature and might not work as expected when mixed with the Options API.


    To ensure reactivity works correctly, you can use a ref directly for currentView and avoid using reactive for the currentComponent. Here's how you can modify your composable and component:

    Composable

    import { ref } from 'vue';
    
    export function useComponentSwitcher() {
      const views = {
        orderDetails: 'orderDetails',
        orderHistory: 'orderHistory',
      };
    
      const currentView = ref(views.orderDetails); // initial view
    
      function switchView(view) {
        currentView.value = view;
      }
    
      return {
        views,
        currentView,
        switchView,
      };
    }
    

    Component

    <template>
      <div>
        <ChildComponentA v-if="currentView === views.orderDetails" />
        <ChildComponentB v-if="currentView === views.orderHistory" />
      </div>
    </template>
    
    <script>
    import { useComponentSwitcher } from '@order-management/composables';
    
    export default {
      setup() {
        const { views, currentView, switchView } = useComponentSwitcher();
    
        // Expose switchView if needed
        return {
          views,
          currentView,
          switchView,
        };
      },
    };
    </script>