diff --git a/src/view/use-announcer/use-announcer.js b/src/view/use-announcer/use-announcer.js index ed571c8269..11e43bbdc9 100644 --- a/src/view/use-announcer/use-announcer.js +++ b/src/view/use-announcer/use-announcer.js @@ -42,9 +42,11 @@ export default function useAnnouncer(contextId: ContextId): Announce { // unmounting after a timeout to let any announcements // during a mount be published setTimeout(function remove() { - // not clearing the ref as it might have been set by a new effect - getBodyElement().removeChild(el); - + // checking if element exists before remove to prevent errors + if (getBodyElement().contains(el)) { + // not clearing the ref as it might have been set by a new effect + getBodyElement().removeChild(el); + } // if el was the current ref - clear it so that // we can get a warning if announce is called if (el === ref.current) { diff --git a/test/unit/view/announcer.spec.js b/test/unit/view/announcer.spec.js index c2b1c3ac8b..5f71f1c0b3 100644 --- a/test/unit/view/announcer.spec.js +++ b/test/unit/view/announcer.spec.js @@ -60,6 +60,26 @@ it('should remove the element when unmounting after a timeout', () => { expect(getElement('5')).not.toBeTruthy(); }); +it('should not remove the element when unmounting after a timeout if the element does not exist', () => { + const { unmount } = render( + {getMock()}, + ); + + const doc = new DOMParser().parseFromString( + 'wat', + 'text/html', + ); + // $FlowFixMe + document.write(doc); + unmount(); + expect(() => jest.runOnlyPendingTimers()).not.toThrow( + // $FlowFixMe + new DOMException( + "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.", + ), + ); +}); + it('should set the text content of the announcement element', () => { // arrange const mock = getMock();