Last active
February 14, 2023 22:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Then we can use "react-portal" to communicate quite conveniently