9/17/2018

Using Vue.js in SharePoint Framework Applications. Part V: Use React Components inside Vue.js Solution

This is the fifth post about SharePoint Framework and Vue.js. In this post I want to show how to use React components inside Vue.js-based SharePoint Framework solutions.

List of posts:
  1. Whats and Whys
  2. Default SPFx web part using Vue.js
  3. Yeoman generator with Vue support
  4. Web Part Property Pane Control
  5. Use React Components inside Vue.js Solution (this post)
Code: https://github.com/AJIXuMuK/vuejs/tree/master/react-in-vue.
In previous posts we discussed practically all the steps and tools that you need to develop SharePoint Framework solution using Vue.js framework.
But there is still uncovered question that might impact on developer's decision to use Vue.js in SPFx solution.
The question is: Can we and How to use React components inside Vue.js projects?
Why should we care about React in Vue? Because Microsoft bets on React and there are a lot of reusable components that simplify SPFx development: Good news everyone! We can use React components inside Vue.js!
And it's actually pretty easy to do because there is an open-source project Vuera that allows you to integrate Vue.js and React.
And let's see how we can use it in SPF solution.

Initial Project Configuration

First, let's create our project.
I'll be using VueSPFx Yeoman generator to provision all Vue.js references that we need automatically.
And let's use Web Part as a playground as it's much easier to debug and render components in there.
yo vuepsfx
After the project has been created we need to reference React libraries:
npm i --save --save-exact react@15.6.2 react-dom@15.6.2 @types/react@15.6.6 @types/react-dom@15.5.6
I'm using --save-exact to reference exactly the same versions of React libraries that are used by default when provisioning SPFx project using OOTB Yeoman Generator with React Framework
Next, let's reference Vuera:
npm i --save vuera
Additionally, let's reference PnP Reusable controls to showcase how to use these components inside Vue.js web part.
npm install @pnp/spfx-controls-react --save --save-exact
Now we have all the configurations in place and let's start development.

Office UI Fabric React Button, WebPartTitle from PnP Reusable Controls and Custom React Component in Vue.js Web Part

In the sample project here I want to show you how to referect Office UI Fabric Button, WebPartTitle component from PnP Reusable controls and custom React component inside a Vue.js web part.
The final UI of the web part will be as simple as that:

Let's first create a custom React component to reference it in the Vue.js web part.
For that let's add ReactComponent folder as a subfolder of components directory and add ReactComponent.tsx file into it:

The content of the file (and of the component) is pretty simple: let's make it stateless with the only property named text in the props object. And let's just render this property inside a div:
import * as React from 'react';

/**
 * Simple component props
 */
export interface IReactComponentProps {
    text: string;
}

/**
 * Sample component with no state
 */
export class ReactComponent extends React.Component<IReactComponentProps, {}> {
    constructor(props: IReactComponentProps) {
        super(props);
    }

    public render(): React.ReactElement<IReactComponentProps> {
        return (<div>{this.props.text}</div>);
    }
}

Now let's look at Vuera's documentation to check how to inject React components into Vue.js (Preferred usage):
As you can see form the screenshot above, Vuera is set up as a Vue.js plugin, and later you can just reference you React component in Vue.js component components property:
components: { 'my-react-component': MyReactComponent }
Unfortunately, it doesn't work if you use vue-class-component decorator as it expects components property to be of type:
components?: { [key: string]: Component<any, any, any, any> | AsyncComponent<any, any, any, any> };
And React components are neither Component nor AsyncComponent that are both Vue.js custom types.
So, we can use another way of injecting React components inro Vue.js that is also mentioned in the documentation (without plugin):

Both options work perfectly. I would prefer the second one as you can use custom elements' names in the template for each component instead of using generic react tag and providing actual component's name as a bind attribute.
Let's apply the "HOC API" approach to our project:
// importing needed components
import { ReactComponent } from '../ReactComponent/ReactComponent';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';

// importing plugin and wrapper
import { VuePlugin, ReactInVue } from 'vuera';

//
// setting up the plugin to work with React components inside Vue
//
Vue.use(VuePlugin);

/**
 * registering components
 */
@Component({
    components: {
        'react-component': ReactInVue(ReactComponent),
        'web-part-title': ReactInVue(WebPartTitle),
        'primary-button': ReactInVue(PrimaryButton)
    }
})
export default class ReactInVueWebPart extends Vue implements IReactInVueWebPartProps {
// ...
Now all the React components can be used in the template (.vue file):
<web-part-title :title="title" :displayMode="displayMode" @updateProperty="_onTitleUpdate" />
<react-component :text="description" />
<primary-button text="Click Me!" @onClick="_onButtonClicked" />
title, displayMode, description are the properties of the Vue.js component and _onTitleUpdate, _onButtonClicked are the methods of the class:
/**
 * Component's properties
 */
export interface IReactInVueWebPartProps {
    description: string;
    title: string;
    displayMode: DisplayMode;
    onButtonClicked: () => void;
    onTitleChanged: (value: string) => void;
}

export default class ReactInVueWebPart extends Vue implements IReactInVueWebPartProps {

    /**
     * implementing ISimpleWebPartProps interface
     */
    @Prop()
    public description: string;
    @Prop()
    public title: string;
    @Prop()
    public displayMode: DisplayMode;
    @Prop()
    public onButtonClicked: () => void;
    @Prop()
    public onTitleChanged: (value: string) => void;
    
    private _onTitleUpdate(value: string): void {
        if (this.onTitleChanged) {
            this.onTitleChanged(value);
        }
    }

    private _onButtonClicked(): void {
        if (this.onButtonClicked) {
            this.onButtonClicked();
        }
    }
}
If you look at the code in the provided repo, there is a commented additional option that can be used:
/**
* Vue component from custom component
*/
//@ReactComponentDecorator(ReactComponent, 'react-component')
//public reactComponent: ReactComponent;
/**
* Vue compoenent from SPFx Reusable React controls repo
*/
//@ReactComponentDecorator(WebPartTitle, 'web-part-title')
//public webPartTitle: WebPartTitle;

/**
* Vue compoenent from Office UI Fabric Primary Buttom
*/
//@ReactComponentDecorator(PrimaryButton, 'primary-button')
//public primaryButton: PrimaryButton;
This approach uses custom ReactComponentDecorator that is located in the same repository and allows to mark Vue.js component's class property as "component" and provide its tag and type. Using this custom decorator we can use "recommended" approach from Vuera and no need to use ReactInVue HOC API (Note: it's not necessary to use this decorator, it's just an additional exercise on how to create custom decorators for Vue.js).

The only left part here is to update web part's code to pass all the properties to the Vue.js component while creating it in render method:
let el = new Vue({
  el: `#${id}`,
  render: h => h(ReactInVueWebPartComponent, {
    props: {
      description: this.properties.description,
      title: this.properties.title,
      displayMode: this.displayMode,
      onTitleChanged: (newTitle: string) => { this.properties.title = newTitle; },
      onButtonClicked: () => { alert('Button Clicked!'); }
    }
  })
});

Conclusion

As a result of this blog post we've created a Vue.js web part that shows how to use React components inside Vue.js SPFx solutions.
It shows how to use all the benefits of Office UI Fabric components, PnP reusable React controls as well as custom developed React components in Vue.js applications.
If you're in love with Vue.js but consider to switch to React because it simplifies SPFx development, now you can stick to Vue.js and just reuse React controls in the same manner if you'd be a React developer.
And here's a small screen recording of the implemented web part:


And that's it for today!
Have fun!

No comments:

Post a Comment