You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractNotificationsContainer.js 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // @flow
  2. import { Component } from 'react';
  3. import { hideNotification } from '../actions';
  4. import { areThereNotifications } from '../functions';
  5. export type Props = {
  6. /**
  7. * The notifications to be displayed, with the first index being the
  8. * notification at the top and the rest shown below it in order.
  9. */
  10. _notifications: Array<Object>,
  11. /**
  12. * Invoked to update the redux store in order to remove notifications.
  13. */
  14. dispatch: Function
  15. };
  16. /**
  17. * Abstract class for {@code NotificationsContainer} component.
  18. */
  19. export default class AbstractNotificationsContainer<P: Props>
  20. extends Component<P> {
  21. /**
  22. * A timeout id returned by setTimeout.
  23. */
  24. _notificationDismissTimeout: ?TimeoutID;
  25. /**
  26. * Initializes a new {@code AbstractNotificationsContainer} instance.
  27. *
  28. * @inheritdoc
  29. */
  30. constructor(props: P) {
  31. super(props);
  32. /**
  33. * The timeout set for automatically dismissing a displayed
  34. * notification. This value is set on the instance and not state to
  35. * avoid additional re-renders.
  36. *
  37. * @type {number|null}
  38. */
  39. this._notificationDismissTimeout = null;
  40. // Bind event handlers so they are only bound once for every instance.
  41. this._onDismissed = this._onDismissed.bind(this);
  42. }
  43. /**
  44. * Sets a timeout for the first notification (if applicable).
  45. *
  46. * @inheritdoc
  47. */
  48. componentDidMount() {
  49. // Set the initial dismiss timeout (if any)
  50. this._manageDismissTimeout();
  51. }
  52. /**
  53. * Sets a timeout if the currently displayed notification has changed.
  54. *
  55. * @inheritdoc
  56. */
  57. componentDidUpdate(prevProps: P) {
  58. this._manageDismissTimeout(prevProps);
  59. }
  60. /**
  61. * Sets/clears the dismiss timeout for the top notification.
  62. *
  63. * @param {P} [prevProps] - The previous properties (if called from
  64. * {@code componentDidUpdate}).
  65. * @returns {void}
  66. * @private
  67. */
  68. _manageDismissTimeout(prevProps: ?P) {
  69. const { _notifications } = this.props;
  70. if (_notifications.length) {
  71. const notification = _notifications[0];
  72. const previousNotification
  73. = prevProps && prevProps._notifications.length
  74. ? prevProps._notifications[0]
  75. : undefined;
  76. if (notification !== previousNotification) {
  77. this._clearNotificationDismissTimeout();
  78. if (notification) {
  79. const { timeout, uid } = notification;
  80. this._notificationDismissTimeout = setTimeout(() => {
  81. // Perform a no-op if a timeout is not specified.
  82. if (Number.isInteger(timeout)) {
  83. this._onDismissed(uid);
  84. }
  85. }, timeout);
  86. }
  87. }
  88. } else if (this._notificationDismissTimeout) {
  89. // Clear timeout when all notifications are cleared (e.g external
  90. // call to clear them)
  91. this._clearNotificationDismissTimeout();
  92. }
  93. }
  94. /**
  95. * Clear any dismissal timeout that is still active.
  96. *
  97. * @inheritdoc
  98. * returns {void}
  99. */
  100. componentWillUnmount() {
  101. this._clearNotificationDismissTimeout();
  102. }
  103. _onDismissed: number => void;
  104. /**
  105. * Clears the running notification dismiss timeout, if any.
  106. *
  107. * @returns {void}
  108. */
  109. _clearNotificationDismissTimeout() {
  110. this._notificationDismissTimeout
  111. && clearTimeout(this._notificationDismissTimeout);
  112. this._notificationDismissTimeout = null;
  113. }
  114. /**
  115. * Emits an action to remove the notification from the redux store so it
  116. * stops displaying.
  117. *
  118. * @param {number} uid - The id of the notification to be removed.
  119. * @private
  120. * @returns {void}
  121. */
  122. _onDismissed(uid) {
  123. const { _notifications } = this.props;
  124. // Clear the timeout only if it's the top notification that's being
  125. // dismissed (the timeout is set only for the top one).
  126. if (!_notifications.length || _notifications[0].uid === uid) {
  127. this._clearNotificationDismissTimeout();
  128. }
  129. this.props.dispatch(hideNotification(uid));
  130. }
  131. }
  132. /**
  133. * Maps (parts of) the Redux state to the associated NotificationsContainer's
  134. * props.
  135. *
  136. * @param {Object} state - The Redux state.
  137. * @private
  138. * @returns {{
  139. * _notifications: Array
  140. * }}
  141. */
  142. export function _abstractMapStateToProps(state: Object) {
  143. const { notifications } = state['features/notifications'];
  144. const _visible = areThereNotifications(state);
  145. return {
  146. _notifications: _visible ? notifications : []
  147. };
  148. }