1

I am using React and hypernova (with php bindings) in order to perform server-side rendering of some React components. Below is my following test component and the response from hypernova.

Test.js

import React from 'react';
import { renderReact } from 'hypernova-react';

class Test extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <p onClick={() => alert('hey')}>click me</p>
    );
  }
}

export default renderReact('Test', Test);

hypernova response

WF\Hypernova\JobResult Object
(
    [error] => 
    [html] => 
        <div data-hypernova-key="Test" data-hypernova-id="e5af0b95-2a31-4ce4-8e36-808605fd4115">
            <p data-reactroot="">click me</p>
        </div>
        <script type="application/json" data-hypernova-key="Test" data-hypernova-id="e5af0b95-2a31-4ce4-8e36-808605fd4115">
            <!--{"prop1":"a","prop2":"b"}-->
        </script>
    [success] => 1
    ...
)

As shown above, props are being successfully passed, but the onClick handler is nowhere to be found. However, it definitely exists in the transpiled code.

bundle.js

// code before and after class omitted for brevity
var Test = function (_React$Component) {
  _inherits(Test, _React$Component);

  function Test(props) {
    _classCallCheck(this, Test);

    return _possibleConstructorReturn(this, (Test.__proto__ || Object.getPrototypeOf(Test)).call(this, props));
  }

  _createClass(Test, [{
    key: 'render',
    value: function render() {
      return _react2.default.createElement(
        'p',
        { onClick: function onClick() {
            return alert('hey');
          } },
        'click me'
      );
    }
  }]);

  return Test;
}(_react2.default.Component);

exports.default = (0, _hypernovaReact.renderReact)('Test', Test);

The only thing I've been able to find on the issue has been in the github issue tracker in which someone complains about the same thing, but apparently there isn't supposed to be an event handler on the <p> tag; it is supposed to exist in code delivered by React. I have also tried assigning a click handler using a class property with/without arrow notation (explicitly binding in the constructor in the latter case). I have added a <script> tag with my bundled React code, but that doesn't appear to make a difference.

Is this a bug, or am I doing something wrong?

Yuki Inoue
  • 3,569
  • 5
  • 34
  • 53
Jared Goguen
  • 8,772
  • 2
  • 18
  • 36

2 Answers2

0

It turns out that, when using server side rendering, the component needs to be rendered both on the server and on the client. In my case, this required creating two separate webpack configs: one for the hypernova server and one for the client React code. Then, I needed to add code like

if (typeof document !== 'undefined') {
  ReactDOM.render(<Test />, document.getElementById('puzzle'));
}

In the parent components so that React would render them on the client and not generate exceptions on the server.

I figured this out from this question.

Jared Goguen
  • 8,772
  • 2
  • 18
  • 36
0

Yeah, this is not enough to make the component properly working.

What you did in Test.js:

...
export default renderReact('Test', Test);

it's actually a server-side rendering of your component Test. So as you see hypernova returns you correctly:

<div data-hypernova-key="Test" data-hypernova-id="e5af0b95-2a31-4ce4-8e36-808605fd4115">
  <p data-reactroot="">click me</p>
</div>
<script type="application/json" data-hypernova-key="Test" data-hypernova-id="e5af0b95-2a31-4ce4-8e36-808605fd4115">
  <!--{"prop1":"a","prop2":"b"}-->
</script>

Apart from this part you need to load client script and run re-hydration of your component (https://reactjs.org/docs/react-dom.html#hydrate). In hypernova you need to prepare another bundle with entrypoint:

// Test.js
const Test = () => {...}
export default Test

// index.js
import Test from './Test'

renderReact('Test', Test); // this will call hydrate when loaded in browser directly

and load this bundle on your index.html page manually:

...
<script src='public/bundle.js'></script>

to help to serve this file, hypernova has in configuration:

hypernova({
  ...,
  createApplication () {
    const app = express()

    app.get('/', (req, res) => res.status(200).json('OK'))

    app.use('/public', express.static(path.join(process.cwd(), 'public')))

    return app
  }
})

Enjoy! Hope it will help you to figure out how to use hypernova.

denieler
  • 237
  • 3
  • 8