Skip to content

Commit 5edfbc7

Browse files
committed
feat: support communication across multiple windows (navigations, reloads)
1 parent 382d0da commit 5edfbc7

File tree

1 file changed

+61
-27
lines changed

1 file changed

+61
-27
lines changed

services/plugin/src/Plugin.tsx

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useDataQuery } from '@dhis2/app-service-data'
33
import postRobot from 'post-robot'
44
import React, {
55
ReactEventHandler,
6+
useCallback,
67
useContext,
78
useEffect,
89
useMemo,
@@ -65,8 +66,7 @@ export const Plugin = ({
6566

6667
const [communicationReceived, setCommunicationReceived] =
6768
useState<boolean>(false)
68-
const [prevCommunicationReceived, setPrevCommunicationReceived] =
69-
useState<boolean>(false)
69+
const [, setPrevCommunicationReceived] = useState<boolean>(false)
7070

7171
const [inErrorState, setInErrorState] = useState<boolean>(false)
7272
const [pluginHeight, setPluginHeight] = useState<string | number>('150px')
@@ -93,18 +93,25 @@ export const Plugin = ({
9393
/* eslint-enable react-hooks/exhaustive-deps */
9494
)
9595

96-
useEffect(() => {
97-
setCommunicationReceived(false)
98-
}, [pluginEntryPoint])
96+
const propsFromParentListenerRef = useRef<any>()
9997

100-
useEffect(() => {
98+
const setUpCommunication = useCallback(() => {
99+
// use function arg to set state here to avoid requiring dependency
100+
let prevCommunication
101+
setPrevCommunicationReceived((prevValue) => {
102+
prevCommunication = prevValue
103+
return communicationReceived
104+
})
101105
// if communicationReceived switches from false to true, the props have been sent
102-
const prevCommunication = prevCommunicationReceived
103-
setPrevCommunicationReceived(communicationReceived)
106+
// the following check avoids sending the props twice in a row
104107
if (prevCommunication === false && communicationReceived === true) {
105108
return
106109
}
107110

111+
if (inErrorState) {
112+
return
113+
}
114+
108115
if (iframeRef?.current) {
109116
const iframeProps = {
110117
...memoizedPropsToPass,
@@ -116,25 +123,30 @@ export const Plugin = ({
116123
}
117124

118125
// if iframe has not sent initial request, set up a listener
119-
if (!communicationReceived && !inErrorState) {
120-
const listener = postRobot.on(
121-
'getPropsFromParent',
122-
// listen for messages coming only from the iframe rendered by this component
123-
{ window: iframeRef.current.contentWindow },
124-
(): any => {
125-
setCommunicationReceived(true)
126-
return iframeProps
127-
}
128-
)
129-
return () => listener.cancel()
126+
if (!communicationReceived) {
127+
// avoid setting up twice
128+
if (!propsFromParentListenerRef.current) {
129+
propsFromParentListenerRef.current = postRobot.on(
130+
'getPropsFromParent',
131+
// listen for messages coming only from the iframe rendered by this component
132+
{ window: iframeRef.current.contentWindow },
133+
(): any => {
134+
setCommunicationReceived(true)
135+
return iframeProps
136+
}
137+
)
138+
}
139+
// return clean-up function
140+
return () => {
141+
propsFromParentListenerRef.current.cancel()
142+
propsFromParentListenerRef.current = null
143+
}
130144
}
131145

132146
// if iframe has sent initial request, send new props
133-
if (
134-
communicationReceived &&
135-
iframeRef.current.contentWindow &&
136-
!inErrorState
137-
) {
147+
// (don't do before to avoid sending messages before listeners
148+
// are ready)
149+
if (iframeRef.current.contentWindow) {
138150
postRobot
139151
.send(
140152
iframeRef.current.contentWindow,
@@ -147,10 +159,32 @@ export const Plugin = ({
147159
})
148160
}
149161
}
150-
// prevCommunicationReceived update should not retrigger this hook
151-
// eslint-disable-next-line react-hooks/exhaustive-deps
152162
}, [memoizedPropsToPass, communicationReceived, inErrorState, alertsAdd])
153163

164+
useEffect(() => {
165+
// return the clean-up function
166+
return setUpCommunication()
167+
}, [setUpCommunication])
168+
169+
const handleLoad = useCallback(
170+
(event) => {
171+
setCommunicationReceived(false)
172+
173+
if (propsFromParentListenerRef.current) {
174+
propsFromParentListenerRef.current.cancel()
175+
propsFromParentListenerRef.current = null
176+
}
177+
178+
// Need to set this up again whenever the iframe contentWindow
179+
// changes (e.g. navigations or reloads)
180+
setUpCommunication()
181+
if (onLoad) {
182+
onLoad(event)
183+
}
184+
},
185+
[onLoad, setUpCommunication]
186+
)
187+
154188
if (data && !pluginEntryPoint) {
155189
return (
156190
<PluginError
@@ -176,7 +210,7 @@ export const Plugin = ({
176210
height: '100%',
177211
border: 'none',
178212
}}
179-
onLoad={onLoad}
213+
onLoad={handleLoad}
180214
></iframe>
181215
</div>
182216
)

0 commit comments

Comments
 (0)