Removing the container element after CLOSE/DESTROY zoid event (after close()), results in uncaught error
See original GitHub issueHi, I’ve running into this trouble and I couldn’t sort it out.
Scenario
- Parent site using React v17.
- User opens an HTML modal, which instantiates and renders a Zoid component (using React driver), as an iframe.
- User Clicks on the background to close it, which internally executes
zoidComponent.close()
to clean the zoid frame. - If enough time has passed, so the iframe was already loaded and rendered properly, this results in no errors ✅.
- But if the user opens the modal and quickly closes it, (seemingly, before the zoid iframe finished loading), then the following uncaught error is thrown 🛑.
- This is happening because the container component from the view, is being set for removal right after the EVENTS.CLOSED zoid event.
- What I’d expect: no error thrown when removing the modal container from the view, after the
EVENTS.CLOSED
(or,EVENTS.DESTROYED
) event - or a way to await for the component to properly be disposed, or a way to handle/catch the error.
zoid.frameworks.frame.js:3201 Uncaught Error: Detected container element removed from DOM
at zoid.frameworks.frame.js:3201
at anonymous::once (zoid.frameworks.frame.js:1045)
at elementClosed (zoid.frameworks.frame.js:3165)
at removeChild (react-dom.development.js:10301)
at unmountHostComponents (react-dom.development.js:21296)
at commitDeletion (react-dom.development.js:21347)
at commitMutationEffects (react-dom.development.js:23407)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
at invokeGuardedCallback (react-dom.development.js:4056)
at commitRootImpl (react-dom.development.js:23121)
at unstable_runWithPriority (scheduler.development.js:646)
at runWithPriority$1 (react-dom.development.js:11276)
at commitRoot (react-dom.development.js:22990)
at performSyncWorkOnRoot (react-dom.development.js:22329)
at react-dom.development.js:11327
at unstable_runWithPriority (scheduler.development.js:646)
at runWithPriority$1 (react-dom.development.js:11276)
at flushSyncCallbackQueueImpl (react-dom.development.js:11322)
at flushSyncCallbackQueue (react-dom.development.js:11309)
at scheduleUpdateOnFiber (react-dom.development.js:21893)
at dispatchAction (react-dom.development.js:16139)
at Object.onDestroy (App.tsx:61)
at zoid.frameworks.frame.js:3320
at zoid.frameworks.frame.js:2746
at Function.ZalgoPromise.try (zoid.frameworks.frame.js:387)
at _loop (zoid.frameworks.frame.js:2745)
at Object.trigger (zoid.frameworks.frame.js:2749)
at zoid.frameworks.frame.js:2967
at Function.ZalgoPromise.try (zoid.frameworks.frame.js:387)
at destroy (zoid.frameworks.frame.js:2966)
at zoid.frameworks.frame.js:2983
at ZalgoPromise._proto.dispatch (zoid.frameworks.frame.js:239)
at ZalgoPromise._proto.then (zoid.frameworks.frame.js:275)
at zoid.frameworks.frame.js:2982
at Function.ZalgoPromise.try (zoid.frameworks.frame.js:387)
at zoid.frameworks.frame.js:2975
at anonymous::memoized (zoid.frameworks.frame.js:998)
at HTMLDivElement.overlay.onclick (pluggy-connect.ts:199)
Context
Below is how the user (parent site) is triggering the “close” action of the iframe which is rendered on the modal. Then, it listens for the zoid CLOSE
/ DESTROY
events, and after that it executes a callback so the container modal can be effectively removed from the view.
containerTemplate({
doc,
dimensions: { height, width },
close,
uid,
frame,
prerenderFrame,
event,
props,
}) {
// ...
// zoid component 'close' event handler
event.on(EVENT.CLOSE, () => {
document.body.classList.remove(modalVisibleClassName);
const { onClose } = props;
// callback for the parent site so it can update the view
if (onClose) {
onClose();
}
});
// zoid component 'destroy' event handler
event.on(EVENT.DESTROY, () => {
const { onDestroy } = props;
// callback for the parent site so it can update the view
if (onDestroy) {
onDestroy();
}
});
// overlay modal close handler
overlay.onclick = () => {
close()
};
// ...
This is how the parent app is instantiating the component:
export const MyReactZoidComponent = ({
tag,
url,
...props
}) => {
const Zoid = useMemo(() => {
zoid.destroy()
return zoid.create({ tag, url }).driver("react", { React, ReactDOM })
}, [tag, url])
return Zoid ? <Zoid {...props} /> : null
}
And this is how the parent site renders it:
function App() {
const [isModalOpened, setIsModalOpened] = useState(false);
const myZoidComponentProps: MyZoidComponentProps = {
url: REACT_APP_CONNECT_URL,
connectToken: REACT_APP_API_KEY,
onError: (error) => {
console.error('Whoops! Error result... ', error);
},
onSuccess: (data) => {
console.log('Yay! Success!', data);
},
onOpen: () => {
console.log('Modal opened.');
},
onClose: () => {
console.log('Pluggy Connect modal closed.');
// setIsModalOpened(false);
},
onDestroy: () => {
console.log('Pluggy Connect modal destroyed.');
try {
setIsModalOpened(false);
} catch (error) {
// NOTE: this catch isn't working, even though the line above shows up in the stack-trace
console.error('whoops..', error);
}
},
};
const openModal = () => {
setIsModalOpened(true);
};
return (
<div className="App">
<button onClick={openModal}>Open Modal</button>
{isModalOpened && <MyReactZoidComponent {...myZoidComponentProps} />}
</div>
);
}
What I’ve tried
- Adding a
try {} catch {}
block arround theclose()
method, and also a promise.catch()
. None have worked.
overlay.onclick = () => {
try {
close().catch((error: unknown) =>
console.warn('Modal close handler resulted in an error ', error)
);
} catch(error) {
console.warn('Caught! Modal close handler resulted in an error ', error)
}
};
- Surrounding the
<Zoid/>
react component with an error boundary. Didn’t work.
**What I’d expect **
- The zoid component
close()
call should not result in an unhandled error, or - The error should be able to be caught/handled using
.catch()
ortry {} catch {}
in some place, or… - A working way to properly await for the
.close()
/EVENTS.CLOSE
action to be fully completed to let me safely remove the container component from the page.
Issue Analytics
- State:
- Created 3 years ago
- Comments:11 (5 by maintainers)
Top Results From Across the Web
krakenjs - Bountysource
Angular app - Stacktrace: Error: Request listener already exists for ... Removing the container element after CLOSE/DESTROY zoid event (after close()), ...
Read more >zoid - bytemeta
zoid repo issues. ... Removing the container element after CLOSE/DESTROY zoid event (after close()), results in uncaught error. MortyPAA. MortyPAA CLOSED.
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Hi @tmilar, I was reviewing your implementation and was wondering if you could solely rely on the zoid close() function for cleaning up the DOM instead of having the React component do it. Ex:
The zoid close() function should be responsible for removing the DOM of the zoid component. There are some MutationObservers being used with zoid and and calling
close()
cleans everything up properly. If you destroy it yourself by having React rip the component out of the DOM then you will end up seeing the error “Uncaught Error: Detected container element removed from DOM”.One other thing to note is the close() function is async. You’ll want ensure that is done before having React remove it (ex:
return Zoid ? <Zoid {...props} /> : null
).any update on this issue? @bluepnume i see this error every time i click the back button before the iframe finishes loading