Search code examples
javascriptreactjsimageplotly.jsreact-share

How to share a dynamically generated image from react with react-share?


I have a react app which generates images on the front end dynamically using Plotly.js. I'd like to add image sharing functionality. I am trying to use react-share for this. Social platforms require image URL for image sharing and do not support images in base64 encoding or alike. Backend was implemented so it can receive images in base64, store in the database and return URL to the image, which is then used for sharing with react-share.

As the image is generated dynamically (it changes each time user resizes the chart, for instance), everything should be done when user clicks on Share icon.

So after the user has clicked on the Share icon, the image generated on the front end should be saved to back end

let imgURI;

  const handleClick = () => {
    Plotly.toImage('chartContainer', {
      format: 'png',
      width: 1000,
      height: 600
    })
      .then(dataUrl => api.post('/image/base64ToPng', { image: dataUrl })
        .then(
          (response) => {
            imgURI = response.data.imgURI;
          },
          failure => console.error(failure)
        ));
  };

after the response is received, passed down to the sharing component like this

<FacebookShareButton
            url={imgURI}
          >
     <FacebookIcon/>
</FacebookShareButton>

The code sample is not asynchronous, so the image URI is not passed to the sharing component, therefore sharing does not work. I tried to pass the prop down using conditional depending on whether it's defined or not and did not come up with a solution. I also looked up some issues in react-share repo that dealt with async urls, but seems like none of them deals with the dynamic image sharing on click.

I'd very appreciate a hint on how to complete this task.


Solution

  • This is serious hack territory, and the whole thing would be a lot simpler if this PR had been completed.

    However, the code below should work (see codesandbox).

    The key steps are:

    1. Have a bit of state that keeps track of whether you have a url from the service or not.
    2. When this state is "none", disable the facebook button's default behavior (i.e. openShareDialogOnClick = false)
    3. Add an onClick handler to the facebook button that asynchronously fetches the url and sets the state (triggering a re-render)
    4. Use an effect + ref so that when the url is set to something real, you manually call the click event on the button (which now has a real address in its url prop), and then re-sets the url to "none"
    import { useEffect, useRef, useState } from "react";
    import { FacebookIcon, FacebookShareButton } from "react-share";
    
    async function getUrFromService(): Promise<string> {
      // The real implementation would make a network call here.
      await new Promise((resolve) => setTimeout(resolve, 1000));
      return "https://via.placeholder.com/150";
    }
    
    export default function App() {
      const shareButton = useRef<HTMLButtonElement>(null);
      const [url, setUrl] = useState<string>("none"); // Unfortunately, we have to have a dummy string here, or FacebookShareButton will blow up.
    
      // Provide an onClick handler that asyncronously fetches the url and sets it in the state.
      const onClick = async () => {
        // Be sure to check for the "none" state, so we don't trigger an infinite loop.
        if (url === "none") {
          const newUrl = await getUrFromService();
          setUrl(newUrl);
        }
      };
    
      // Whenever "url" changes and we re-render, we manually fire the click event on the button, and then re-set the url.
      useEffect(() => {
        if (url !== "none") {
          shareButton.current?.click();
          setUrl("none");
        }
      }, [url, shareButton]);
    
      return (
        <FacebookShareButton
          ref={shareButton}
          // Disable calling the dialog if we don't have a url yet.
          openShareDialogOnClick={url !== "none"}
          url={url}
          onClick={onClick}
        >
          <FacebookIcon />
        </FacebookShareButton>
      );
    }