Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popup content is non-interactive (react components are rendered as static markup in Popup content) #182

Closed
rdo-mgowland opened this issue Oct 27, 2023 · 7 comments

Comments

@rdo-mgowland
Copy link

rdo-mgowland commented Oct 27, 2023

I have buttons that display in my popups that I need the user to be able to click.
I believe the click events on the buttons within a popup are not working becuase when rendering the popup content you are using renderToStaticMarkup.

I think useCreateAzureMapPopup should be changed from this:

const [popupRef] = useState<atlas.Popup>(
    new atlas.Popup({ ...options, content: renderToStaticMarkup(popupContent) })
  )

To actually render the react component so it can have interactive content.

@rdo-mgowland rdo-mgowland reopened this Oct 30, 2023
@rdo-mgowland rdo-mgowland changed the title Popup content is rendered as renderToStaticMarkup which makes it non-interactive Popup content is non-interactive (react components are rendered as static markup in Popup content) Oct 30, 2023
@rdo-mgowland
Copy link
Author

rdo-mgowland commented Oct 30, 2023

I have come up with this work around, but its ugly as hell. There's probably a better way of doing this; I'm no React expert by any stretch. But it works.

const memoizedPopup: IAzureMapChildren = useMemo(() => {
    const popupContainerDiv = document.getElementById("popup-container-1");

    if (!popupContainerDiv && !popupContainerDivIsRendered && myPopupData) {
      // force re-render after popup div container exists in DOM
      setTimeout(() => setPopupContainerDivIsRendered(true), 10);
      // Render the popup div container so we can create portal to it on next render
      return <AzureMapPopup isVisible={true} options={popupOptions} popupContent={<div id="popup-container-1" />} />;
    } else if (popupContainerDiv && myPopupData) {
      return (
        <>
          <AzureMapPopup isVisible={!!myPopupData} options={popupOptions} popupContent={<div id="popup-container-1" />} />
          {createPortal(
            <MyCustomPopup machine={myPopupData} onClose={() => setMyPopupData(undefined)} />,
            popupContainerDiv as HTMLElement
          )}
        </>
      );
    } else {
      return <AzureMapPopup isVisible={!!myPopupData} options={popupOptions} popupContent={<div id="popup-container-1" />} />;
    }
  }, [myPopupData, popupContainerDivIsRendered]);

  return (
    <AzureMap options={option}>
      <AzureMapDataSourceProvider id={"MyAzureMapDataSourceProvider"}>
        <AzureMapLayerProvider
          id={"MyAzureMapLayerProvider"}
          type={markersLayer}
          events={{
            click: (event: MapMouseEvent) => {
              if (event.shapes && event.shapes.length > 0) {
                const prop: any = event.shapes[0];
                const myMarkerProps: any = prop.data.properties;
                if (myMarkerProps) {
                  setMyPopupData(myMarkerProps);
                  setPopupOptions({
                    ...popupOptions,
                    position: [prop.data.geometry.coordinates[0], prop.data.geometry.coordinates[1]],
                    closeButton: false,
                  });
                }
              }
            }
          }}
        />
        {memoizedMarkerRender}
      </AzureMapDataSourceProvider>
      <AzureMapPopup isVisible={!!myPopupData} options={popupOptions} popupContent={<div id="popup-container-1" />} />
      {memoizedPopup}
    </AzureMap>
  );

Would be really nice if you could please facilitate passing a JSX element to AzureMapPopup popupContent which gets rendered as "real interactive react" instead of static non interactive react (not sure on the terminology here, but i'm running with anyway). Maybe if you pass react children into the AzureMapPopup component it could render it as "real interactive react".
I would attempt a pull request myself, but a) I'm not skilled enough to come up with an elegant solution, b) I couldnt figure out how to even run the code after cloning the repo lol.

@yulinscottkang
Copy link
Contributor

One possible approach would be to create a custom Popup component, similar to <AzureMapPopup>, and utilize ReactDOM.render to generate the content for the popup. That'll probably be more flexible for your use case.

You'll need to use the <AzureMapsContext> from react-azure-maps to get a mapRef reference, and create an instance of atlas.Popup that you can attach to the map by calling popup.open(mapRef). Similar to what useCreateAzureMapPopup.ts does now.

I believe @adityagupta-msft has implemented something similar, so maybe he can also provide a code snippet.

@rdo-mgowland
Copy link
Author

Could we get this added to this library? Surely a lot of people would want this functionality.

@rdo-mgowland
Copy link
Author

@adityagupta-msft

I really really need to be able to display interactive React within a popup. It blows my mind that no one else is wanting this. Can I please please please get an example of how to do this. @adityagupta-msft you were referenced as maybe having a solution?
Thanks

@adityagupta-msft
Copy link

adityagupta-msft commented Nov 23, 2023

Hi @rdo-mgowland, sorry I missed this. The solution I came up with is the following:

import { useContext, useEffect, useState } from "react";
import ReactDOM from "react-dom";
import atlas from "azure-maps-control";
import {
  IAzureMapsContextProps,
  MapType,
  IAzureMapPopup,
} from "react-azure-maps/dist/types/types";
import { AzureMapsContext } from "react-azure-maps";

export const GeoMapPopup = ({ isVisible, popupContent, options, events }: IAzureMapPopup) => {
  const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext);
  const popupRef = useCreatePopup({ options, popupContent, isVisible });

  useCheckRefMount<MapType, boolean>(mapRef, true, (mref) => {
    events &&
      events.forEach(({ eventName, callback }) => {
        mref.events.add(eventName, popupRef, callback);
      });
    return () => {
      mref.popups.remove(popupRef);
    };
  });

  useEffect(() => {
    if (mapRef) {
      if (isVisible && !popupRef.isOpen()) {
        popupRef.open(mapRef);
      } else if (
        mapRef.popups.getPopups().length &&
        !isVisible &&
        popupRef.isOpen()
      ) {
        popupRef.close();
      }
    }
  }, [isVisible]);

  return null;
}

const useCreatePopup = ({
  options,
  popupContent,
  isVisible,
}: Pick<IAzureMapPopup, "popupContent" | "options" | "isVisible">) => {
  const containerRef = document.createElement("div");
  ReactDOM.render(popupContent, containerRef);

  const [popupRef] = useState<atlas.Popup>(
    new atlas.Popup({ ...options, content: containerRef })
  );
  const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext);

  useEffect(() => {
    const containerRef = document.createElement("div");
    ReactDOM.render(popupContent, containerRef);
    popupRef.setOptions({
      ...options,
      content: containerRef,
    });
    if (mapRef && isVisible && !popupRef.isOpen()) {
      popupRef.open(mapRef);
    }
  }, [options, popupContent]);

  return popupRef;
};

function useCheckRefMount<T, T1>(
  dep: T | null,
  on: T1 | null,
  callback: (dep: T, on: T1) => void
) {
  useEffect(() => {
    if (dep && on) {
      return callback(dep, on);
    }
  }, []);
}

It takes functionality from useCreateAzureMapPopup.ts and I created a new popup component called GeoMapPopup which can be used.

I am not sure about the efficiency of the solution as it requires creating a new DOM element every time but I hope it helps!

@yulinscottkang
Copy link
Contributor

See @adityagupta-msft's comment for a working solution.

@yulinscottkang
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants