Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access DOM event-handlers for JSX-components in server-sided React

I am building a simple static view-engine using React with the goal of rendering static HTML-markup and generating a js-file filled with that components DOM-events (onClick, etc).

The way I'm doing the first part is to require a specified JSX-file which, for example, looks like this:

import React from 'React';

export default class Test extends React.Component {

    clicked() {
        alert('Clicked the header!');
    }

    render() {
        return (
            <html>
                <head>
                    <title>{this.props.title}</title>
                </head>
                <body>
                    <h1 onClick={this.clicked}>click me!!!</h1>
                </body>
            </html>
        );
    }
}

I am then rendering the JSX-file via a NodeJS-backend like this:

let view = require('path-to-the-jsx-file');
view = view.default || view;

const ViewElement = React.createFactory(view);
let output = ReactDOMServer.renderToStaticMarkup(ViewElement(props));

It works great for serving static HTML. But I am wondering if there is a way to access all components used in the JSX-file in an array or something, which I then could use to check what events are bound and to which handlers.

So in this example, be able to get that the <h1>-tag's onClick-handler? Is this even possible to do somehow?

like image 203
Tokfrans Avatar asked Oct 11 '17 20:10

Tokfrans


2 Answers

To be able to get the function as a string from the onClick event, we want the following:

  1. The DOM of the element
    • We can obtain this by attaching a ref attribute on our h1 element
  2. The name of the function being passed into the onClick event (clicked)
  3. The function itself from a string containing the name of the function
    • Since we're conveniently using methods within a React component, we can use this['functionName'] within our component to obtain the function.
  4. A stringified version of the function

import React from 'React';

export default class Test extends React.Component {
    componentDidMount() {

        // Gets the name of the function passed inside onClick()
        const nameBound = this.element.props.onClick.name;

        // Removes 'bound ' from the function name (-> clicked)
        const nameString = nameBound.replace('bound ', '');

        // Gets the function from the function name as a string
        const convertedFunction = this[nameString];

        // Converts the function into string
        const stringifiedFunction = convertedFunction.toString();
        console.log(functionString);
    }

    clicked() {
        alert('Clicked the header!');
    }

    render() {
        return (
            <html>
                <head>
                    <title>{this.props.title}</title>
                </head>
                <body>
                    <h1 ref={(element) => { this.element = element; }} onClick={this.clicked}>click me!!!</h1>
                </body>
            </html>
        );
    }
}
like image 109
AstroBoogie Avatar answered Sep 29 '22 17:09

AstroBoogie


After a lot of messing around I came up with a solution that works quite well.

If I create my own instance of the ReactElement I want to render (in the example ViewElement(props)), I can then render the element using it's standard render-function:

let element = ViewElement(props);
let instance = new element.type();
let render = instance.render();

From here I can go through all the props for this element, so, say, onClick-handlers would be in render.props.

So what I do is to check each prop if the key matches a react-event-name (ex. onClick, onDragEnd, onDragEnter etc). If it does, and the value of this property is of type function - I have the event-name and it's handler-function:

Object.keys(render.props).map((key) => {
    if (bigArrayOfAllTheEventNames.indexOf(key) !== -1) {
        item.events[key] = render.props[key];//check if type is function.
    }
});

Then I also iterate through the render.props.children recursivly to reach all it's child components and add every component which has events to an array.

The only problem left was that I needed a way to bind the rendered DOM-string to the javascript handlers I now have. For this I added a need to use a custom DOM-attribute, which then can be used to ID the component with something like this

$("[data-id=value]").on({event-name}, {it's JS-handler}).

It might not be perfect yet, but I think that this is the best solution out there.

like image 29
Tokfrans Avatar answered Sep 29 '22 17:09

Tokfrans