From f2a92c2d22345568ca7f47ee1d1d70de53eb547d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 12 Sep 2024 10:16:07 +0200 Subject: [PATCH] Fix notifications re-rendering spuriously in web UI (#31879) --- .../components/notification_mention.tsx | 4 ++- .../features/notifications_v2/index.tsx | 3 ++- .../features/ui/containers/modal_container.js | 4 ++- .../ui/containers/notifications_container.js | 19 +++----------- app/javascript/mastodon/selectors/index.js | 25 ++++++++++++++----- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_mention.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_mention.tsx index 1929446bb2..d53cb37a83 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_mention.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_mention.tsx @@ -1,5 +1,7 @@ import { FormattedMessage } from 'react-intl'; +import { isEqual } from 'lodash'; + import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply-fill.svg?react'; import { me } from 'mastodon/initial_state'; @@ -47,7 +49,7 @@ export const NotificationMention: React.FC<{ status.get('visibility') === 'direct', status.get('in_reply_to_account_id') === me, ] as const; - }); + }, isEqual); let labelRenderer = mentionLabelRenderer; diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx index 29c49e05c8..730d95bcd5 100644 --- a/app/javascript/mastodon/features/notifications_v2/index.tsx +++ b/app/javascript/mastodon/features/notifications_v2/index.tsx @@ -4,6 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Helmet } from 'react-helmet'; +import { isEqual } from 'lodash'; import { useDebouncedCallback } from 'use-debounce'; import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react'; @@ -62,7 +63,7 @@ export const Notifications: React.FC<{ multiColumn?: boolean; }> = ({ columnId, multiColumn }) => { const intl = useIntl(); - const notifications = useAppSelector(selectNotificationGroups); + const notifications = useAppSelector(selectNotificationGroups, isEqual); const dispatch = useAppDispatch(); const isLoading = useAppSelector((s) => s.notificationGroups.isLoading); const hasMore = notifications.at(-1)?.type === 'gap'; diff --git a/app/javascript/mastodon/features/ui/containers/modal_container.js b/app/javascript/mastodon/features/ui/containers/modal_container.js index 1c3872cd50..63c568f847 100644 --- a/app/javascript/mastodon/features/ui/containers/modal_container.js +++ b/app/javascript/mastodon/features/ui/containers/modal_container.js @@ -3,10 +3,12 @@ import { connect } from 'react-redux'; import { openModal, closeModal } from '../../../actions/modal'; import ModalRoot from '../components/modal_root'; +const defaultProps = {}; + const mapStateToProps = state => ({ ignoreFocus: state.getIn(['modal', 'ignoreFocus']), type: state.getIn(['modal', 'stack', 0, 'modalType'], null), - props: state.getIn(['modal', 'stack', 0, 'modalProps'], {}), + props: state.getIn(['modal', 'stack', 0, 'modalProps'], defaultProps), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/mastodon/features/ui/containers/notifications_container.js b/app/javascript/mastodon/features/ui/containers/notifications_container.js index 3d60cfdad1..b8aa9bc461 100644 --- a/app/javascript/mastodon/features/ui/containers/notifications_container.js +++ b/app/javascript/mastodon/features/ui/containers/notifications_container.js @@ -4,24 +4,11 @@ import { connect } from 'react-redux'; import { NotificationStack } from 'react-notification'; -import { dismissAlert } from '../../../actions/alerts'; -import { getAlerts } from '../../../selectors'; - -const formatIfNeeded = (intl, message, values) => { - if (typeof message === 'object') { - return intl.formatMessage(message, values); - } - - return message; -}; +import { dismissAlert } from 'mastodon/actions/alerts'; +import { getAlerts } from 'mastodon/selectors'; const mapStateToProps = (state, { intl }) => ({ - notifications: getAlerts(state).map(alert => ({ - ...alert, - action: formatIfNeeded(intl, alert.action, alert.values), - title: formatIfNeeded(intl, alert.title, alert.values), - message: formatIfNeeded(intl, alert.message, alert.values), - })), + notifications: getAlerts(state, { intl }), }); const mapDispatchToProps = (dispatch) => ({ diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index bd9b53919c..10e1b167ca 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -7,14 +7,16 @@ import { me } from '../initial_state'; export { makeGetAccount } from "./accounts"; -const getFilters = (state, { contextType }) => { - if (!contextType) return null; +const getFilters = createSelector([state => state.get('filters'), (_, { contextType }) => contextType], (filters, contextType) => { + if (!contextType) { + return null; + } - const serverSideType = toServerSideType(contextType); const now = new Date(); + const serverSideType = toServerSideType(contextType); - return state.get('filters').filter((filter) => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now)); -}; + return filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now)); +}); export const makeGetStatus = () => { return createSelector( @@ -73,10 +75,21 @@ const ALERT_DEFAULTS = { style: false, }; -export const getAlerts = createSelector(state => state.get('alerts'), alerts => +const formatIfNeeded = (intl, message, values) => { + if (typeof message === 'object') { + return intl.formatMessage(message, values); + } + + return message; +}; + +export const getAlerts = createSelector([state => state.get('alerts'), (_, { intl }) => intl], (alerts, intl) => alerts.map(item => ({ ...ALERT_DEFAULTS, ...item, + action: formatIfNeeded(intl, item.action, item.values), + title: formatIfNeeded(intl, item.title, item.values), + message: formatIfNeeded(intl, item.message, item.values), })).toArray()); export const makeGetNotification = () => createSelector([