Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use React.hydrate when using vue?

Tags:

Bit of an odd situation here - I have a website written in Vue and I want to demo a library I've written in react. I can avoid server side rendering (SSR) by wrapping ReactDOM.hydrate(ReactApp, document.getElementById('react'area')) but I don't want to do that. I want to render everything SSR, but I don't see how it's possible.

Here is my renderOnServer.js for vue:

process.env.VUE_ENV = 'server'  const fs = require('fs') const path = require('path')  const filePath = './App/dist/server.js' const code = fs.readFileSync(filePath, 'utf8')  const vue_renderer = require('vue-server-renderer').createBundleRenderer(code)  //prevent XSS attack when initialize state var serialize = require('serialize-javascript') var prerendering = require('aspnet-prerendering')  module.exports = prerendering.createServerRenderer(function (params) {   return new Promise((resolve, reject) => {       const context = {         url: params.url,         absoluteUrl: params.absoluteUrl,         baseUrl: params.baseUrl,         data: params.data,         domainTasks: params.domainTasks,         location: params.location,         origin: params.origin,         xss: serialize("</script><script>alert('Possible XSS vulnerability from user input!')</script>")       }       const serverVueAppHtml = vue_renderer.renderToString(context, (err, _html) => {         if (err) { reject(err.message) }         resolve({           globals: {             html: _html,             __INITIAL_STATE__: context.state           }         })       })     }) }); 

So basically I'm configuring SSR above to read server.js:

import { app, router, store } from './app'  export default context => {   return new Promise((resolve, reject) => {     router.push(context.url)      router.onReady(() => {       const matchedComponents = router.getMatchedComponents()        if (!matchedComponents.length) {         return reject(new Error({ code: 404 }))       }        Promise.all(matchedComponents.map(Component => {         if (Component.asyncData) {           return Component.asyncData({ store, context })         }       }))         .then(() => {           context.state = store.state           resolve(app)         })         .catch(reject)     }, reject)   }) } 

and server.js above is just looking for the right vue component and rendering. I have a test react component:

import React from 'react'  export default class ReactApp extends React.Component {     render() {       return (         <div>Hihi</div>       )     }   } 

and my vue component:

<template>   <div id="page-container">     <div id="page-content">       <h3 class="doc-header">Demo</h3>        <div id="react-page">        </div>     </div>   </div> </template> <script> <script>   import ReactApp from './ReactApp.jsx'   import ReactDOM from 'react-dom'    export default {       data() {           return {           }       },   }    ReactDOM.hydrate(ReactApp, document.getElementById('#react-page')) </script> 

But obviously it won't work because I can't use document in SSR.

like image 956
Fred Johnson Avatar asked Feb 15 '19 23:02

Fred Johnson


People also ask

Can I use Vue and react together?

Yes. You can pass children from React to Vue and back as you usually would.

What is hydrate in Vue?

Hydration refers to the client-side process during which Vue takes over the static HTML sent by the server. and turns it into dynamic DOM that can react to client-side data changes.

How does react hydrate work?

React hydration is a technique used that is similar to rendering, but instead of having an empty DOM to render all of our react components into, we have a DOM that has already been built, with all our components rendered as HTML.


1 Answers

Basically, the purpose of hydrate is to match react DOM rendered in browser to the one that came from the server and avoid extra render/refresh at load.

As it was pointed in the comments hydrate should be used on the client-side and React should be rendered on the server with renderToString.

For example, on the server it would look something like this:

const domRoot = (   <Provider store={store}>     <StaticRouter context={context}>       <AppRoot />     </StaticRouter>   </Provider> ) const domString = renderToString(domRoot)  res.status(200).send(domString) 

On the client:

<script>   const domRoot = document.getElementById('react-root')    const renderApp = () => {     hydrate(       <Provider store={store}>         <Router history={history}>           <AppRoot />         </Router>       </Provider>,       domRoot     )   }    renderApp() </script> 

Technically, you could render React components server-side and even pass its state to global JS variable so it is picked up by client React and hydrated properly. However, you will have to make a fully-featured react rendering SSR support(webpack, babel at minimum) and then dealing with any npm modules that are using window inside (this will break server unless workarounded).

SO... unless it is something that you can't live without, it is easier, cheaper and faster to just render React demo in the browser on top of returned Vue DOM. If not, roll up your sleeves :) I made a repo some time ago with react SSR support, it might give some light on how much extra it will be necessary to handle.

To sum everything up, IMO the best in this case would be to go with simple ReactDOM.render in Vue component and avoid React SSR rendering:

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="my-compiled-react-bundle.js"></script> <script>   function init() {     ReactDOM.render(ReactApp, document.getElementById('#react-page'))   }   init();   </script> 
like image 177
Max Gram Avatar answered Sep 20 '22 08:09

Max Gram