Is it ok to use ReactDOMServer.renderToString in the browser in areas where React isn't directly managing the DOM?

ReactjsLeafletReact Dom

Reactjs Problem Overview


I'm working on an app using Leaflet (via react-leaflet). Leaflet directly manipulates the DOM. The react-leaflet library doesn't change that, it just gives you React components that you can use to control your Leaflet map in a React-friendly way.

In this app, I want to use custom map markers that are divs containing a few simple elements. The way to do that in Leaflet is to set your marker's icon property to a DivIcon, in which you can set your custom HTML. You set that inner HTML by setting the DivIcon's html property to a string containing the HTML. In my case, I want that HTML to be rendered from a React component.

In order to do that, it seems like the correct approach is to use ReactDOMServer.renderToString() to render the Component that I want inside the map marker into a string, which I would then set as the html property of the DivIcon:

MyMarker.js:

import React, { Component } from 'react'
import { renderToString } from 'react-dom/server'
import { Marker } from 'react-leaflet'
import { divIcon } from 'leaflet'

import MarkerContents from './MarkerContents'

export class MyMarker extends Component {
  render() {
    const markerContents = renderToString(<MarkerContents data={this.props.data} />)
    const myDivIcon = divIcon({
      className: 'my-marker',
      html: markerContents
    })
    
    return (
      <Marker
        position={this.props.position}
        icon={myDivIcon} />
    )
  }
}

However, according to the React docs:

> This [renderToString] should only be used on the server.

Is this a strict rule, or is it only meant to dissuade people from circumventing ReactDOM's efficient management of the DOM?

I can't think of another (better) way to accomplish what I'm after. Any comments or ideas would be greatly appreciated.

Reactjs Solutions


Solution 1 - Reactjs

According to the new documentation: https://reactjs.org/docs/react-dom-server.html

> The following methods can be used in both the server and browser > environments: > > - renderToString() > - renderToStaticMarkup()

Solution 2 - Reactjs

I know it is too old question, but since it has not been answered I wanted to share my thoughts.

I was using the same thing, renderToString, but as the documentation recommends not to use it on client-side, I achieved it in another way, by using the react-dom's render method to render the custom component into div

var myDiv = document.createElement('div');

ReactDOM.render(
  <MarkerContents data={this.props.data} />,
  myDiv
);

var myIcon = L.divIcon({ 
    iconSize: new L.Point(50, 50), 
    html: myDiv.innerHTML
});

Solution 3 - Reactjs

As Thomas already said, yes, you can use renderToString on the client. Just to be clear though, you will need to import ReactDOMServer on the client, which may seem counter-intuitive but appears to be correct. Example (on the client):

import React from 'react';
import ReactDOMServer from 'react-dom/server';

const MyComp = (props) => {
  const html = ReactDOMServer.renderToString(<div>{someFunc(props)}</div>);
  // do something with your html, then
  return <div dangerouslySetInnerHTML={{__html: html}}></div>;
};

Solution 4 - Reactjs

I had the exact same problem with leaflet and ended up solving the problem by thinking with portals.

import React, { useEffect, useMemo,useState } from "react";
import ReactDOM from "react-dom";
import { useLeafletContext } from "@react-leaflet/core";
import * as L from "leaflet";

/**
 * @type { React.FC<{
 *    positionX?: number
 *    positionY?: number
 *    width?: number
 *    height?:number
 * }> }
 * */
const LeafletChild = ({ children, positionX=0, positionY=0, width, height }) => {
  const context = useLeafletContext();
  const [divElement] = useState(() => document.createElement("div"));
  const icon = useMemo(() => L.divIcon({ iconSize: new L.Point(height, width), html: divElement }), [height,width, divElement]);
  const marker = useMemo(() => L.marker([0,0], { icon }), [icon]);
  useEffect(()=>{
    marker.setLatLng([positionY, positionX])
  },[positionY,positionX, marker])

  useEffect(() => {
    const container = context.layerContainer || context.map;
    container.addLayer(marker);
    return () => container.removeLayer(marker);
  }, [context, marker]);
  return ReactDOM.createPortal(children, divElement);
};

Somwhere else...

<LeafletChild>
  <div onClick={()=>setSomeBool(!someBool)}>
    anything you want here like...{`${someBool}`}
  </div>
</LeafletChild>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShane CavaliereView Question on Stackoverflow
Solution 1 - ReactjsThomas GraingerView Answer on Stackoverflow
Solution 2 - ReactjsMohamed ShaabanView Answer on Stackoverflow
Solution 3 - ReactjsChristian FritzView Answer on Stackoverflow
Solution 4 - ReactjsRobert AronView Answer on Stackoverflow