-
Notifications
You must be signed in to change notification settings - Fork 16
/
Alerts.js
97 lines (90 loc) · 3.5 KB
/
Alerts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { useAlerts } from '@dhis2/app-service-alerts'
import { AlertStack, AlertBar } from '@dhis2/ui'
import React, { useCallback, useState } from 'react'
/* The alerts-manager which populates the `useAlerts` hook from
* `@dhis2/app-service-alerts` hook with alerts only supports
* simply adding and removing alerts. However, the `AlertBar`
* from `@dhis2/ui` should leave the screen with a hide-animation.
* This works well, for alerts that hide "naturally" (after the
* timeout expires or when the close icon is clicked). In these
* cases the componsent will request to be removed from the alerts-
* manager after the animation completes. However, when
* programatically hiding an alert this is the other way around:
* the alert is removed from the alerts-manager straight away and
* if we were to render the alerts from the `useAlerts` hook, these
* alerts would be removed from the DOM abrubdly without an animation.
* To prevent this from happening, we have implemented the
* `useAlertsWithHideCache` hook:
* - It contains all alerts from the alert-manager, with
* `options.hidden` set to `false`
* - And also alerts which have been removed from the alert-manager,
* but still have their leave animation in progress, with
* `options.hidden` set to `true`
* - Alerts are removed once the `onHidden` callback fires */
const useAlertsWithHideCache = () => {
const [alertsMap] = useState(new Map())
/* We don't use this state value, it is used to trigger
* a rerender to remove the hidden alert from the DOM */
const [, setLastRemovedId] = useState(null)
const alertManagerAlerts = useAlerts()
const updateAlertsFromManager = useCallback(
(newAlerts = []) => {
const newAlertsIdLookup = new Set()
newAlerts.forEach((alert) => {
newAlertsIdLookup.add(alert.id)
if (!alertsMap.has(alert.id)) {
// new alerts, these are not hiding
alertsMap.set(alert.id, {
...alert,
options: {
...alert.options,
hidden: alert.options.hidden || false,
},
})
}
})
// alerts in alertsMap but not in newAlerts are hiding
alertsMap.forEach((alert) => {
if (!newAlertsIdLookup.has(alert.id)) {
alert.options.hidden = true
}
})
},
[alertsMap]
)
const removeAlert = useCallback(
(id) => {
alertsMap.delete(id)
setLastRemovedId(id)
},
[alertsMap]
)
updateAlertsFromManager(alertManagerAlerts)
return {
alerts: Array.from(alertsMap.values()).sort((a, b) => a.id - b.id),
removeAlert,
}
}
const Alerts = () => {
const { alerts, removeAlert } = useAlertsWithHideCache()
return (
<AlertStack>
{alerts.map(
({ message, remove, id, options: { onHidden, ...props } }) => (
<AlertBar
{...props}
key={id}
onHidden={() => {
onHidden && onHidden()
removeAlert(id)
remove()
}}
>
{message}
</AlertBar>
)
)}
</AlertStack>
)
}
export { Alerts }