Skip to content

Instantly share code, notes, and snippets.

@IniZio
Created June 21, 2019 03:32
Show Gist options
  • Save IniZio/68872edc1acf2ecfadf5ffffa06f321a to your computer and use it in GitHub Desktop.
Save IniZio/68872edc1acf2ecfadf5ffffa06f321a to your computer and use it in GitHub Desktop.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const context = React.createContext<{
setElement?: React.Dispatch<React.SetStateAction<Element | null | undefined>>;
element?: Element;
}>({});
export function createTeleporter() {
function useTargetRef() {
const ctx = React.useContext(context);
return React.useCallback(element => {
if (ctx.setElement) {
ctx.setElement(element);
} else {
ctx.element = element;
}
}, []);
}
function Target<TC extends React.JSXElementConstructor<any>>({
as: As = 'div',
...props
}: { as: TC } & React.ComponentProps<TC>) {
const handleRef = useTargetRef();
return <As ref={handleRef} {...props} />;
}
const createSource = ({ placeholder }: { placeholder?: boolean } = {}) => {
function Source({ children }: { children?: React.ReactNode }) {
const ctx = React.useContext(context);
const [element, setElement] = React.useState<Element | null | undefined>(
null
);
React.useLayoutEffect(() => {
ctx.setElement = setElement;
setElement(ctx.element);
return () => {
ctx.setElement = undefined;
};
}, []);
if (!element) return null;
return !placeholder || ctx.setElement === setElement
? ReactDOM.createPortal(children, element)
: null;
}
return Source;
};
return {
Source: createSource(),
Target,
Placeholder: createSource({ placeholder: true }),
useTargetRef
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment