35

I'm working on a map-based app that uses Google Map API to create markers and its info window in React.js. The infowindow.setContent() only accepts either a String or HTML. It's impossible for me to pass in String as I have a button that links to a specific method in another react component (something like: _this.props.addList(place) ). Thus I must fill the argument as HTML DOM as the following lines of code:

var div = document.createElement('div');
var title = document.createElement('h4');
title.innerHTML = place.name;

var btn = document.createElement('button');
btn.className = 'btn btn-danger btn-block';
btn.appendChild(document.createTextNode('I want to go here !!'));

div.appendChild(title).appendChild(btn);

google.maps.event.addListener(marker, 'click', function() {

  infowindow.setContent( div );
  infowindow.open(map, this);
});

btn.addEventListener('click', function() {
  _this.props.addList(place);
});

The codes work for me but I don't wanna create elements one by one. I've also tried to pass the argument with a React component but it seems not working:

createMarker: function() {
  
  /** Some other lines of code */

  var _this = this;

  google.maps.event.addListener(marker, 'click', function() {

    infowindow.setContent( _this._renderInfoWindow(place) );
    infowindow.open(map, _this);

  });

},

// my infowindow rendering method
_renderInfoWindow: function(place) {
  return(
    <div>
      <h4>{place.name}</h4>
      <p>{place.cost}</p>
      <button className="btn btn-danger btn-block" onClick={this.props.addList.bind(this, place)}>I want to go here !! </button>
    </div>
  )
},

so is there another way to at least convert a react component to HTML so that I don't have to write document.createElement() one by one?

Thanks

7 Answers 7

25

You can render a ReactElement in a detached DOM Node via React.render. Thus, the following code should work for you.

createMarker: function() {

  /** Some other lines of code */

  _this = this;

  google.maps.event.addListener(marker, 'click', function() {

    var div = document.createElement('div');
    ReactDOM.render( _this._renderInfoWindow(place), div );
    infowindow.setContent( div );
    infowindow.open(map, this);

  });

},
Sign up to request clarification or add additional context in comments.

2 Comments

That was part of Dewey's code snippet. Unless _this was defined higher in the function body, it will indeed bleed to the global scope.
You should use ReactDOM.render instead, not React.render.
17

You could also use React's renderToString() method

_renderInfoWindow: function(place) {
  return React.renderToString(
    <div>
      <h4>{place.name}</h4>
      <p>{place.cost}</p>
      <button className="btn btn-danger btn-block" onClick={this.props.addList.bind(this, place)}>I want to go here !! </button>
    </div>
  );
}

This should work for a simple component as shown. React.renderToString() will return only the html for the component.

3 Comments

I used import ReactDOMServer from 'react-dom/server' and ReactDOMServer.renderToStaticMarkup`
The button event handler does not work using this approach.
I recommend against using ReactDOMServer, it will require polyfills to run on browsers from Webpack v5+.
17

For newer versions of React

import ReactDOMServer from "react-dom/server";

let html = ReactDOMServer.renderToString(<div>...</div>)

Comments

8

This is how it's supposed to be done in React 18:

import { createRoot } from 'react-dom/client';
import { flushSync } from 'react-dom';

const div = document.createElement('div');
const root = createRoot(div);
flushSync(() => {
  root.render(<MyIcon />);
});
console.log(div.innerHTML); // For example, "<svg>...</svg>"

The main part I was missing while trying to migrate to React 18 was the flushSync part. Before adding it my innerHtml always returned empty string.

According to this post: https://react.dev/reference/react-dom/server/renderToString#removing-rendertostring-from-the-client-code

The flushSync call is necessary so that the DOM is updated before reading its innerHTML property.

Also, renderToString and importing react-dom/server into the browser is still not recommended.

2 Comments

this gave me an error. ` Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.`
Can you post a sample?
4

This should render the HTML.

import ReactDOMServer from "react-dom/server";

const html = ReactDOMServer.renderToString(<div>...</div>)

Comments

1

Alexandre's method is now deprecated and was never idiomatic. This is the new way, written idiomatically:

createMarker: function() {

  /** Some other lines of code */

  _this = this;

  google.maps.event.addListener(marker, 'click', function() {

    var div = document.createElement('div');
    const root = ReactDOM.createRoot(div)
    root.render(<InfoWindow place={place} addList={...} />));
    infowindow.setContent(div);
    infowindow.open(map, this);

  });

},

// my infowindow rendering method
function InfoWindow ({ place, addList }) {
  return(
    <div>
      <h4>{place.name}</h4>
      <p>{place.cost}</p>
      <button className="btn btn-danger btn-block" onClick={() => addList(place)}>I want to go here !! </button>
    </div>
  )
},

Notice the mixing of JSX into the click handler and the extraction of InfoWindow into its own component: idiomatic React.

The next issue to fix is the creation of multiple roots on the same node. This can be fixed by keeping in to a single, shared one:

constructor() {
  this.infoWindowDiv = document.createElement('div');
  this.infoWindowReactRoot = ReactDOM.createRoot(this.infoWindowDiv)
}

and later...

this.infoWindowReactRoot.render(<InfoWindow place={place} addList={...} />));

3 Comments

Error: 'ReactDOM is undefined'
error 2: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
@johnk You can find solutions to both of these by searching the errors
0
// Create a DOM element to hold the react component.
var span = document.createElement('span');
// Render the React component.
React.render(h.button(null, 'Buttonz!'), span);
// This will give the result as a string, which is useful if you've escaped
// React's context (e.g. inside DataTables).
// Obviously, the component will no longer be reactive but this is a
// good way to leverage simple stuff you've already written in React.
// In my case, I'm primarily leveraging React wrappers around Bootstrap components.
// The important thing is that componentDidMount gets called.
return span.outerHTML;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.