11// @flow
22import { useRef , useEffect } from 'react' ;
33import { useMemo , useCallback } from 'use-memo-one' ;
4- import { invariant } from '../../invariant' ;
54import type { Announce , ContextId } from '../../types' ;
65import { warning } from '../../dev-warning' ;
76import getBodyElement from '../get-body-element' ;
@@ -14,42 +13,48 @@ export default function useAnnouncer(contextId: ContextId): Announce {
1413 const id : string = useMemo ( ( ) => getId ( contextId ) , [ contextId ] ) ;
1514 const ref = useRef < ?HTMLElement > ( null ) ;
1615
17- useEffect ( ( ) => {
18- invariant ( ! ref . current , 'Announcement node already mounted' ) ;
19-
20- const el : HTMLElement = document . createElement ( 'div' ) ;
21- ref . current = el ;
22-
23- // identifier
24- el . id = id ;
25-
26- // Aria live region
27-
28- // will force itself to be read
29- el . setAttribute ( 'aria-live' , 'assertive' ) ;
30- el . setAttribute ( 'role' , 'log' ) ;
31- // must read the whole thing every time
32- el . setAttribute ( 'aria-atomic' , 'true' ) ;
33-
34- // hide the element visually
35- Object . assign ( el . style , visuallyHidden ) ;
36-
37- // Add to body
38- getBodyElement ( ) . appendChild ( el ) ;
39-
40- return ( ) => {
41- // unmounting after a timeout to let any annoucements
42- // during a mount be published
43- setTimeout ( function remove ( ) {
44- const toBeRemoved : ?HTMLElement = ref . current ;
45- invariant ( toBeRemoved , 'Cannot unmount announcement node' ) ;
46-
47- // Remove from body
48- getBodyElement ( ) . removeChild ( toBeRemoved ) ;
49- ref . current = null ;
50- } ) ;
51- } ;
52- } , [ id ] ) ;
16+ useEffect (
17+ function setup ( ) {
18+ const el : HTMLElement = document . createElement ( 'div' ) ;
19+ // storing reference for usage in announce
20+ ref . current = el ;
21+
22+ // identifier
23+ el . id = id ;
24+
25+ // Aria live region
26+
27+ // will force itself to be read
28+ el . setAttribute ( 'aria-live' , 'assertive' ) ;
29+ el . setAttribute ( 'role' , 'log' ) ;
30+ // must read the whole thing every time
31+ el . setAttribute ( 'aria-atomic' , 'true' ) ;
32+
33+ // hide the element visually
34+ Object . assign ( el . style , visuallyHidden ) ;
35+
36+ // Add to body
37+ getBodyElement ( ) . appendChild ( el ) ;
38+
39+ return function cleanup ( ) {
40+ // Not clearing the ref as it might be used by announce before the timeout expires
41+
42+ // unmounting after a timeout to let any announcements
43+ // during a mount be published
44+ setTimeout ( function remove ( ) {
45+ // not clearing the ref as it might have been set by a new effect
46+ getBodyElement ( ) . removeChild ( el ) ;
47+
48+ // if el was the current ref - clear it so that
49+ // we can get a warning if announce is called
50+ if ( el === ref . current ) {
51+ ref . current = null ;
52+ }
53+ } ) ;
54+ } ;
55+ } ,
56+ [ id ] ,
57+ ) ;
5358
5459 const announce : Announce = useCallback ( ( message : string ) : void => {
5560 const el : ?HTMLElement = ref . current ;
0 commit comments