Skip to content

Instantly share code, notes, and snippets.

@gotexis
Last active February 14, 2023 22:42
Show Gist options
  • Save gotexis/61f370012b77756c8e01590a3e48a8bb to your computer and use it in GitHub Desktop.
Save gotexis/61f370012b77756c8e01590a3e48a8bb to your computer and use it in GitHub Desktop.
Display a modal, from child react app inside iframe, to parent react app
// Child component
// =======================================================================
import React, { useState } from 'react';
function componentToJson(component) {
let jsonTree = {};
if (typeof component === 'string') {
return { type: 'text', value: component };
} else {
jsonTree.type = component.type;
jsonTree.props = component.props;
if (component.children) {
jsonTree.children = [];
for (const child of component.children) {
jsonTree.children.push(componentToJson(child));
}
}
return jsonTree;
}
}
const useStateTunnelReceiver = (elementId, initialState) => {
const [state, setState] = useState(initialState);
useEffect(() => {
window.addEventListener('message', (event) => {
if (event.data.elementId === elementId) {
// for onChange event
setState(event.data.event.target.value);
}
});
}, []);
return [state, setState];
};
const useClickTunnelReceiver = (elementId, callback) => {
useEffect(() => {
window.addEventListener('message', (event) => {
if (event.data.elementId === elementId) {
callback();
}
});
}, []);
};
const ModalRoot = styled.div`
position: fixed;
top: 0;
`;
const Modal = () => <ModalRoot class="modal">
This is the content of the modal
<input type="text" onChangeCallback={{
elementId: 'input-DEVICE_VERIFICATION_CODE',
// When the input value changes, sends a message to the parent component.
// Our JSX renderer is going to strip the "Callback" & render this as "onChange"
}} />
<button
onClickCallback={{
elementId: 'SUBMIT_DEVICE_VERIFICATION_CODE',
}}
// When the button is clicked, sends a message to the parent component.
// Our JSX renderer is going to strip the "Callback" & render this as "onClick"
>
Submit
</button>
</ModalRoot>
const Child = () => {
const [code] = useStateTunnelReceiver('input-DEVICE_VERIFICATION_CODE', '');
useClickTunnelReceiver('SUBMIT_DEVICE_VERIFICATION_CODE', () => {
// submit logic
submit(code);
});
const handleShowModal = () => {
const modalReactJsonTree = componentToJson(Modal);
window.parent.postMessage({
type: 'SHOW_MODAL',
modalReactJsonTree,
});
};
return (
<div>
<button onClick={handleShowModal}> Show Modal </button>
</div>
);
};
// Parent component - iframe host
=======================================================================
// =======================================================================
// =======================================================================
import React, { useState, useEffect } from 'react';
function jsonToComponent(jsonTree, iframeRef) {
if (jsonTree.type === 'text') {
return jsonTree.value;
} else {
for (const prop in jsonTree.props) {
if (jsonTree.props[prop].endsWith('Callback')) {
const eventType = prop.replace('Callback', ''); // onChange, onClick, onSubmit, etc
const { elementId } = jsonTree.props[prop];
jsonTree.props[eventType] = (event) => {
iframeRef.postMessage(
{
elementId,
eventType,
event,
},
'*'
);
};
}
}
let component = React.createElement(
jsonTree.type,
jsonTree.props,
...(jsonTree.children || []).map(jsonToComponent)
);
return component;
}
}
const ParentIframeHost = () => {
const [showModal, setShowModal] = useState(false);
const [modalReactJsonTree, setModalReactJsonTree] = useState();
useEffect(() => {
// register open / close event listener
window.addEventListener('message', (event) => {
if (event.data.type === 'SHOW_MODAL') {
setShowModal(true);
setModalReactJsonTree(event.data.modalReactJsonTree);
} else if (event.data.type === 'CLOSE_MODAL') {
setShowModal(false);
}
});
}, []);
return (
<div>
{showModal && jsonToComponent(modalReactJsonTree, iframeRef.current)}
<IframeResizer ref={iframeRef} />
</div>
);
};
@gotexis
Copy link
Author

gotexis commented Feb 14, 2023

Imagine we have 2 iframes loading the same component, one just for the sake of modals.

These 2 iframes can probably access the DOM of each other and probably render a react component, and manage it. 

parent.nameofotheriframe.getElementById("myvideo"); 

 Then we can use "react-portal" to communicate quite conveniently

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment