Search code examples
vue.jselement-ui

Vue Js pass all context when wrapping components with functional components


I am creating some custom components based on Element UI. I have two issues at the moment: - Pass all the context down from the wrapper to the component; - When I click on the select element in the following snippet the event does not trigger the change of currentValue. I tried also with @onchange="setValue" :value="currentValue", but nothing changed.

Obviously if I use Select and Option as they come with Element UI, they do work as supposed.

The reason why I need to wrap the components is that I need to add some default classes and brand them with some custom CSS.

---CustomSelect.js

import Vue from 'vue';
import { Select } from 'element-ui';
import classnames from 'classnames';
import 'element-theme-chalk/src/select.scss';
import './select.scss';

export default Vue.component('ExampleSelect', {
  functional: true,

  render(h, context) {
    console.log('ExampleSelect context', context);
    function wrappedComponent() {
      return Select;
    }

    function getExtendedClassName() {
      return classnames('example-select', {
        [context.props.classNames]: context.props.classNames
      });
    }

    return h(
      wrappedComponent(),
      {
        class: getExtendedClassName(),
        parent: context.parent && Object.keys(context.parent).length > 0 && context.parent,
        data: context.data && Object.keys(context.data).length > 0 && context.data,
        props: context.props && Object.keys(context.props).length > 0 && context.props,
        injections:
          context.injections && Object.keys(context.injections).length > 0 && context.injections,
        listeners:
          context.listeners && Object.keys(context.listeners).length > 0 ? context.listeners : {}
      },
      context.children && Object.keys(context.children).length > 0 && context.children
    );
  }
});

---CustomOption.js

import Vue from 'vue';
import { Option as ExampleOption } from 'element-ui';
import classnames from 'classnames';
import 'element-theme-chalk/src/option.scss';
import './option.scss';

export default Vue.component('ExampleOption', {
  functional: true,

  render(h, context) {
    console.log('ExampleSelect option', context);
    function wrappedComponent() {
      return ExampleOption;
    }

    function getExtendedClassName() {
      return classnames('example-option', {
        [context.props.classNames]: context.props.classNames
      });
    }

    return h(
      wrappedComponent(),
      {
        class: getExtendedClassName(),
        parent: context.parent && Object.keys(context.parent).length > 0 && context.parent,
        data: context.data && Object.keys(context.data).length > 0 && context.data,
        props: context.props && Object.keys(context.props).length > 0 && context.props,
        injections:
          context.injections && Object.keys(context.injections).length > 0 && context.injections,
        listeners:
          context.listeners && Object.keys(context.listeners).length > 0 ? context.listeners : {}
      },
      context.children && Object.keys(context.children).length > 0 && context.children
    );
  }
});

Thank you in advance for your help.


Solution

  • I solved the issue. So it looks like the names of the properties in the data object https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth

    Are different from the names of the properties in context: https://v2.vuejs.org/v2/guide/render-function.html#Functional-Components

    Maybe a suggestion for the future is to make them match, or create an utility that maps them allowing to pass them all at once like that.

    This is useful in the context of hocs where you want to delegate the main functionality to the received component and you just want to change a few details and make them default.

    Therefore, this is the correct return statement:

        return h(
          wrappedComponent(),
          {
            class: getExtendedClassName(),
            name: 'ExampleInput',
            componentName: 'ExampleInput',
            props: context.props,
            slots: context.slots(),
            scopedSlots: context.scopedSlots,
            data: context.data,
            parent: context.parent,
            on: context.listeners,
            inject: context.injections,
          },
          context.children
        );