How pass props to children in createPortal function

Viewed 443

I created a WindowPortal to open many new external windows/tabs.

https://codesandbox.io/s/modest-goldwasser-q6lig?file=/src/App.js

How can I pass props to a props.children in createPortal function?

I want pass newWindow as a prop for handling resize of new window.

import React, { useState, useRef, useEffect, useCallback, Children, cloneElement } from "react";
import { createPortal } from "react-dom";

import { create } from "jss";
import { jssPreset, StylesProvider, CssBaseline } from "@material-ui/core";

type WindowPortalProps = {
  width: number;
  height: number;
  close: () => void;
  id: number;
  title: string;
};

const WindowPortal: React.FC<WindowPortalProps> = (props) => {
  const [container, setContainer] = useState<HTMLElement | null>(null);
  const newWindow = useRef<Window | null>(null);
  const { title } = props;
  const [jss, setJss] = useState<any>(null);

  useEffect(() => {
    // Create container element on client-side
    setContainer(document.createElement("div"));
  }, []);

  const close = useCallback(() => {
    props.close();
  }, [props]);

  useEffect(() => {
    // When container is ready
    if (container) {
      setJss(create({ ...jssPreset(), insertionPoint: container }));
      // Create window
      newWindow.current = window.open(
        "",
        "",
      `width=${props.width},height=${props.height},left=200,top=200,scrollbars,resizable,menubar,toolbar,location`,
      ) as Window;
      // Append container
      newWindow.current.document.body.appendChild(container);
      newWindow.current.document.title = title;

      const stylesheets = Array.from(document.styleSheets);
      stylesheets.forEach((stylesheet) => {
        const css = stylesheet as CSSStyleSheet;
        const owner = stylesheet.ownerNode as HTMLElement;
        if (owner.dataset.jss !== undefined) {
          // Ignore JSS stylesheets
          return;
        }
        if (stylesheet.href) {
          const newStyleElement = document.createElement("link");
          newStyleElement.rel = "stylesheet";
          newStyleElement.href = stylesheet.href;
          newWindow.current?.document.head.appendChild(newStyleElement);
        } else if (css && css.cssRules && css.cssRules.length > 0) {
          const newStyleElement = document.createElement("style");
          Array.from(css.cssRules).forEach((rule) => {
            newStyleElement.appendChild(document.createTextNode(rule.cssText));
          });
          newWindow.current?.document.head.appendChild(newStyleElement);
        }
      });

      // Save reference to window for cleanup
      const curWindow = newWindow.current;
      curWindow.addEventListener("beforeunload", close);
      // Return cleanup function
      return () => {
        curWindow.close();
        curWindow.removeEventListener("beforeunload", close);
      };
    }
  }, [container]);

  return (
    container &&
    newWindow.current &&
    jss &&
    createPortal(
      <StylesProvider jss={jss} sheetsManager={new Map()}>
        <CssBaseline />
        {props.children} -> how pass newWindow.current as externalWindow props?
      </StylesProvider>,
      container,
    )
  );
};

const ExpandedComponentForWindowPortal = ({ externalWindow, ...rest }) => {
   const [countOfResize, setCountOfResize] = useState(0);

   const doSomething = () => setCountOfResize(countOfResize + 1);

   useEffect(() => {
     externalWindow.addEventListener("resize", doSomething);
     return () => {
       externalWindow.removeEventListener("resize", doSomething);
       setCountOfResize(0)
     }
   }, []);

   return <></>;
}
const ComponentInExternalWindow = () => {
 const closeWindow = () => {}
 return (
   <WindowPortal
    title={"title"}
    width={window.innerWidth}
    heigth={window.innerHeigth}
    key={2}
    id={1}
    close={closeWindow}
   >
     <ExpandedComponentForWindowPortal />
  </WindowPortal>  
 )
}
1 Answers

I would suggest using a context for this use case. In WindowPortalHooks:

const WindowPortalContext = createContext(null);

export const useWindowPortalContext = () => {
  return useContext(WindowPortalContext);
};

and in the end of the function wrap the children with the context provider

 return (
    container &&
    createPortal(
      <WindowPortalContext.Provider
        value={{
          close
        }}
      >
        {props.children}
      </WindowPortalContext.Provider>,
      container
    )
  );

Then in any child rendered by MyWindowPortal you could use useWindowPortalContext:

function Expanded(props) {
  const { close } = useWindowPortalContext();

  return (
    <div>
      <Typography align="center">{props.title}</Typography>
      <button type="button" onClick={close}>
        close me
      </button>
    </div>
  );
}

Edit loving-hill-5hmmq

Related