mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.23k stars 2.23k forks source link

Using `createRoot` (instead of `ReactDOM.render`) to render a custom popup for a marker #12653

Closed magnusrodseth closed 1 year ago

magnusrodseth commented 1 year ago

mapbox-gl-js version: ^2.13.0

Question

I have map with some markers that have a custom popover. This custom popover was implemented in the following way:

// Creating popup node
export const createPopupNode = ({ sensor }: CreatePopupNodeProps) => {
  const popupNode = document.createElement("div");

  ReactDOM.render(
    <SensorMarkerPopover
      title={sensor.name}
      content={sensor.location}
      link={`sensors/${sensor.id}`}
      linkLabel="See more"
    />,
    popupNode,
  );

  return popupNode;
};
// Defining my own interface for interacting with `mapboxgl.Popup`
export const MapboxPopup = ({ html }: PopupOptions) =>
  new mapboxgl.Popup({
    closeOnMove: true,
    closeButton: false,
    offset: 25,
  }).setHTML(html.innerHTML);

// Attaching the custom popup from `createPopupNode(...)` to the marker
const popup = MapboxPopup({ html: createPopupNode({ sensor }) });
return MapboxMarker({
  latitude: sensor.latitude,
  longitude: sensor.longitude,
  color: slate["400"],
  addTo: map.current,
}).setPopup(popup);

This works, but I get the following error in the browser console:

Warning: ReactDOM.render is no longer supported in React 18. 
Use createRoot instead. 
Until you switch to the new API, your app will behave as if it's running React 17. 
Learn more: https://reactjs.org/link/switch-to-createroot.

Of course, I want to use React 18's functionality. Hence, I'm trying to convert the creation of a popup node (createPopupNode) to use createRoot instead of ReactDOM.render(...).

I figured it would be something like the following:

// Converting `ReactDOM.render(...)` to `createRoot(...)`
export const createPopupNode = ({ sensor }: CreatePopupNodeProps) => {
  const popupNode = document.createElement("div");
  const root = createRoot(popupNode);

  root.render(
    <SensorMarkerPopover
      title={sensor.name}
      content={sensor.location}
      link={`sensors/${sensor.id}`}
      linkLabel="See more"
    />,
  );

  return popupNode;
};

This returned value would need to be passed into the MapboxPopup parameter, but this doesn't work. Specifically, the custom popup on the marker is completely empty. To me, this suggests that the DOM element const popupNode = document.createElement("div"); never renders with the <SensorMarkerPopover /> component.

How do I use createRoot to render a custom popup for a marker?

Links to related documentation

stepankuzmin commented 1 year ago

Hi @magnusrodseth,

I assume the issue is with your React setup, not the GL JS. You can probably consider using Portals to render Mapbox Popup content instead of rendering the content as React root element.

Just so you know, this issue tracker is for reporting bugs and feature requests, so I'm closing the issue. Feel free to reopen if you have any questions. You might also consider posting your question to https://stackoverflow.com/questions/tagged/mapbox-gl-js to ask the community for help.