Using React components directly in Vue components (with or without Typescript) — Part 1 — Overview
This is an overview of my experience with integrating VueJS and ReactJS in the same codebase, writing all code with JSX, and using one Webpack build. For a more in-depth step-by-step tutorial on how to set this up yourself. Please see my Part 2 — Full Tutorial in which I provide a step-by-step walkthrough of how to set up a Vue project that can directly make use of React components. I then highlight how to use some of the core features of React in that Vue project.
Short Disclaimer: I would highly suggest that you become familiar with Vue and React individually before experimenting with this concept in too much depth. Furthermore, it is always a good idea to do your homework before committing anything to a Production environment.
- Vuera, an open-source library on Github, can be used to create relatively simple interoperability between Vue and React using JSX for writing consistent code across both tools.
- Being able to use both Vue and React components in the same project (and file) opens up the opportunity to leverage the resources and components from both the Vue and React ecosystems.
- Link to Github repository featuring full code example here
Recently, one of my business partners started building an incredible B2B application using Vue and managed to make a ton of progress. At a certain point, I decided to start contributing to his project and began prototyping with Vue. I managed to achieve a lot with Vue but found myself in need of certain components which were either poorly maintained, a work in progress, or simply did not exist. This is by no means a fault of Vue as a framework, but instead, I believe it’s more so because a lot of the more popular and powerful React components were either made or supported by one of the tech giants, a trendy startup or a smart consulting firm. My personal belief is that it’s just a matter of time before more high-budget patrons adopt Vue for various reasons which will even out the playing field, but that’s definitely a long and speculative conversation for another time. Anyway, rather than spend the time to build those components myself from scratch and deal with maintaining them, I decided to venture on a quest to see if I could seamlessly integrate some of the more high-end React components out there into our Vue application. A quick search led me to only one reliable result which is a fascinating open-source library called Vuera. Vuera provides a brilliant way to integrate React components into Vue components and vice versa without losing a ton of core functionality specific to Vue or React. While Vuera is still in its early stages, it’s still a strong potential resolution to the age-old Vue vs. React battle which has riled up a ton of contention in the front-end world.
Vuera and JSX
At first, I followed the Vuera docs completely just to get acquainted with the tool. I thought the integration was absolutely fantastic however, it didn’t immediately work directly with JSX. Writing React without JSX is possible but absolutely not recommended by pretty much anyone that you ask in the React community. As an enterprising React developer, my first instinct was to figure out how to integrate React components with JSX into my Vue app. Fortunately, Vue offers the ability to explicitly use render functions with JSX as oppose to Single File Components (SFCs) which feature an html templating DSL. As someone who just happens to really enjoy using the JSX syntax, I prefer it very much over the Vue templating system but I recognize the pros and cons of both. The trick is to pass the component through the Higher-Order Component (HOC) wrapper functions provided by Vuera without registering your component to your Vue instance. The output of the HOC returns exactly what babel-plugin-transform-vue-jsx is expecting as an input in your Vue render function. I explain with details and examples in my Part 2 — Full Tutorial post. Note that this particular technique only works for React components loaded in from external modules. Any components that you write yourself will need to be resolved with a small non-invasive little tweak to your Vue Webpack configuration that I came up with and will detail below.
JSX and Babel in a Nutshell
The Render Function and Vue’s ‘h’ Function
Another important piece of context to understand before this will all make sense is Vue’s ‘h’ function. You can read more on the Vue docs:
Render Functions & JSX — Vue.js
Vue recommends using templates to build your HTML in the vast majority of cases. There are situations however, where…
They do an excellent job of explaining this so I won’t go into too much detail. Here’s a basic comparison of what Vue and React look like without the JSX syntax sugar:
By default, Vue’s render function definition expects the ‘h’ function to be passed into scope. This ‘h’ function is actually just a commonly used shorthand pseudonym for ‘createElement’. The render function is simply a function that gets registered with a Vue component which lets you specify to how the Vue instance should stamp your elements into the DOM. Think of it as a more explicit set of instructions than using Vue templates. Of course, React’s internals are a different but the concept of render functions being used to manage the DOM is very similar. In order to be more efficient, both Vue and React intelligently track and handle these changes to the DOM internally in what is referred to as the Virtual DOM. Using the transform-vue-jsx babel plugin, we can create the equivalent of our above components with JSX:
Depending on your version of the babel JSX transform plugin however, you must supply ‘h’ as an argument to your Vue render function or else things will break because ‘h is not defined’ and thus it has no factory function for creating Vue elements. Recent updates to transform-vue-jsx have made it auto-inject the ‘h’ as a parameter so you don’t need to do it anymore in your Vue components as of transform-vue-jsx version 3.4.0:
babel plugin for vue 2.0 jsx. Contribute to vuejs/babel-plugin-transform-vue-jsx development by creating an account on…
That said, when Webpack loads in your Vue and React components, it looks for .jsx files (or .tsx if you’re using typescript). By default, webpack is told to use the babel-loader to handle JSX because it contains the transform-vue-jsx babel plugin. As mentioned above, this plugin is responsible for transforming JSX into something that Vue can understand. The problem is that since Vue and React components can both have .jsx (or .tsx) file extensions, Webpack doesn’t know which JSX transformer plugin to use and tries to use the Vue transformer to handle both Vue and React files. The irony here is that Vue JSX and React JSX look almost 100% equivalent yet it still fails. The only difference is that funny little character ‘h’ which has to be available in the scope of your Vue render function to work. So if the Vue JSX parser tries to parse your React file, it will break with error ‘h is not defined’. All you need to do to resolve this is add one more rule into your Webpack configuration to tell the babel-loader how to distinguish between Vue’s and React’s JSX files.
This considers TSX files as well. In addition, you will need to suffix all of the React files in your project with .react.jsx (or .react.tsx). What this does is tell Webpack to associate your babel-loader with the React JSX transformer for .react.tsx or .react.jsx files otherwise default to using the Vue JSX transformer. This was just one possible non-invasive solution that I’ve come up with but, I’m very much open to other ideas if anyone can think of a better way. Also, don’t worry about external React component modules. You don’t need to rename anything for those to work. Of course, don’t forget to add react and react-dom as dependencies to your project for this process to work. You may also run into the following error when using the react babel loader:
URIError: Failed to decode param ‘/%3C%=%20BASE_URL%20%%3Efavicon.ico’
To resolve this one you’ll need to rename your public folder (I chose “vpublic” as in “vue-public”) and clone your favicon.ico into an empty public folder in your root. I don’t really like the idea of having to change the name of the public folder and I understand that a lot of people might be averse to doing so. I’m currently looking into better ways to do this but, if anyone has any recommendations for a better solution, feel free to drop me a line! Your resulting project structure should look like this:
The Vue documentation points out specifically that not all components are best written using Vue templates. So while I acknowledge that this is not the end-all-be-all solution to the debate between frameworks, it is simply another angle on how we can use these tools to achieve our unified goal of creating powerful, reactive and robust user interfaces. Ideally, I feel as though this is a means to allowing teams to more fluidly handle the transition between frameworks regardless of their personal interests. I personally appreciate when there are ways to try new tools and frameworks in a practical setting before making too much of an initial investment and this library makes that possible. We don’t have to argue about it anymore people. With a little compromise and support from the community, everyone can get what they want without so much conflict if we work together.
P.S. While Vuera is actually a really awesome name, my suggestion for a more fitting name would be TruceJS because it feels to me like a consensus between Vue and React developers. I think many of us can agree that lack of consensus has been both a major catalyst and inhibitor to the world of front-end development.