akxcv / vuera

:eyes: Vue in React, React in Vue. Seamless integration of the two. :dancers:
MIT License
4.3k stars 242 forks source link

Cannot be used in asynchronously required Vue components #59

Open Axure opened 6 years ago

Axure commented 6 years ago

If you use it the VuePlugin, and then use asynchronously required components with vue-router, React would complain: Objects are not valid as a React child (found: [object Promise]).

johannes-z commented 5 years ago

@akxcv The problem is this check: https://github.com/akxcv/vuera/blob/d365b07c609ae3cc2fb83e0fd03183c1573dfb11/src/utils/isReactComponent.js#L6-L11

This also happens when using dynamic imports for components:

  components: {
    MyComponent: () => import(
      /* webpackChunkName: 'MyComponent' */
      '@/components/MyComponent.vue'
    ),
  }

The problem is, that the function checks for a constructor and its super type, but the component is just a simple factory function, so isReactComponent returns true...

Edit: I think something like this would work before the lines of code I posted from the original function, but it triggers the promise resolution so there is no benefit in using dynamic imports...

  try {
    if (component() instanceof Promise) return false
  } catch (err) {
    console.warn(err);
  }

Edit2: Another hack I found that works better than the previous one, as it doesn't resolve the promise:

if (component.toString().indexOf('return __webpack_require__') > -1) return false

Basically it checks if the function returns a dynamic import... Using this hack, this code should avoid unnecessary work:

function isReactComponent(component) {
  if ((typeof component === 'undefined' ? 'undefined' : _typeof(component)) === 'object' && !isReactForwardReference(component)) {
    return false;
  }

  let isVue = typeof component === 'function' && component.prototype && component.prototype.constructor.super && component.prototype.constructor.super.isVue
  if (isVue) return false

  if (component.toString().indexOf('return __webpack_require__') > -1) return false

  return true
}
EricRabil commented 3 years ago

Workaround that prevents writing a fork:

export function markLazyVue<T extends Function>(fn: T): T {
    if (!fn.prototype) fn.prototype = {};
    if (!fn.prototype.constructor) fn.prototype.constructor = function() { return; };
    if (!fn.prototype.constructor.super) fn.prototype.constructor.super = {};
    if (!fn.prototype.constructor.super.isVue) fn.prototype.constructor.super.isVue = true;
    return fn;
}

Usage:

{
  components: {
    Tooltip: markLazyVue(() => import("@/components/core/Tooltip.vue"))
  }
}