From 4e48d7792b495f3fe47b1fccdda66298d7d65983 Mon Sep 17 00:00:00 2001
From: Josh Perez <60019601+josh-signal@users.noreply.github.com>
Date: Tue, 8 Mar 2022 14:11:11 -0500
Subject: [PATCH] Use a hook for the ever-updating now
---
ts/components/StoryListItem.tsx | 1 -
ts/components/StoryViewer.tsx | 1 -
ts/components/StoryViewsNRepliesModal.tsx | 3 ---
.../conversation/CallingNotification.tsx | 4 +--
.../ChangeNumberNotification.stories.tsx | 2 --
.../conversation/ChangeNumberNotification.tsx | 5 ++--
.../conversation/Message.stories.tsx | 2 --
ts/components/conversation/Message.tsx | 6 -----
ts/components/conversation/MessageAudio.tsx | 3 ---
ts/components/conversation/MessageDetail.tsx | 19 +------------
.../conversation/MessageMetadata.tsx | 3 ---
.../conversation/MessageTimestamp.stories.tsx | 1 -
.../conversation/MessageTimestamp.tsx | 4 +--
ts/components/conversation/Quote.stories.tsx | 1 -
.../conversation/Timeline.stories.tsx | 3 ---
ts/components/conversation/Timeline.tsx | 17 +-----------
ts/components/conversation/TimelineItem.tsx | 3 ---
ts/hooks/useNowThatUpdatesEveryMinute.ts | 27 +++++++++++++++++++
ts/state/smart/MessageAudio.tsx | 1 -
ts/state/smart/Timeline.tsx | 3 ---
ts/state/smart/TimelineItem.tsx | 3 ---
21 files changed, 34 insertions(+), 78 deletions(-)
create mode 100644 ts/hooks/useNowThatUpdatesEveryMinute.ts
diff --git a/ts/components/StoryListItem.tsx b/ts/components/StoryListItem.tsx
index 9ba87ebce4..992e4b94cb 100644
--- a/ts/components/StoryListItem.tsx
+++ b/ts/components/StoryListItem.tsx
@@ -163,7 +163,6 @@ export const StoryListItem = ({
>
diff --git a/ts/components/StoryViewer.tsx b/ts/components/StoryViewer.tsx
index 4232351bca..de8863d7b2 100644
--- a/ts/components/StoryViewer.tsx
+++ b/ts/components/StoryViewer.tsx
@@ -233,7 +233,6 @@ export const StoryViewer = ({
diff --git a/ts/components/StoryViewsNRepliesModal.tsx b/ts/components/StoryViewsNRepliesModal.tsx
index a91f224c00..72b7d66377 100644
--- a/ts/components/StoryViewsNRepliesModal.tsx
+++ b/ts/components/StoryViewsNRepliesModal.tsx
@@ -249,7 +249,6 @@ export const StoryViewsNRepliesModal = ({
@@ -285,7 +284,6 @@ export const StoryViewsNRepliesModal = ({
@@ -324,7 +322,6 @@ export const StoryViewsNRepliesModal = ({
diff --git a/ts/components/conversation/CallingNotification.tsx b/ts/components/conversation/CallingNotification.tsx
index 38a97662ce..ba4e8a6b40 100644
--- a/ts/components/conversation/CallingNotification.tsx
+++ b/ts/components/conversation/CallingNotification.tsx
@@ -32,13 +32,12 @@ type PropsHousekeeping = {
i18n: LocalizerType;
conversationId: string;
nextItem: undefined | TimelineItemType;
- now: number;
};
type PropsType = CallingNotificationType & PropsActionsType & PropsHousekeeping;
export const CallingNotification: React.FC = React.memo(props => {
- const { i18n, now } = props;
+ const { i18n } = props;
let timestamp: number;
let wasMissed = false;
@@ -67,7 +66,6 @@ export const CallingNotification: React.FC = React.memo(props => {
(
(
story.add('Long name', () => (
= props => {
- const { i18n, now, sender, timestamp } = props;
+ const { i18n, sender, timestamp } = props;
return (
= props => {
i18n={i18n}
/>
ยท
-
+
>
}
icon="phone"
diff --git a/ts/components/conversation/Message.stories.tsx b/ts/components/conversation/Message.stories.tsx
index ed95f81997..a8e93ee4bf 100644
--- a/ts/components/conversation/Message.stories.tsx
+++ b/ts/components/conversation/Message.stories.tsx
@@ -84,7 +84,6 @@ const MessageAudioContainer: React.FC = props => {
audio={audio}
computePeaks={computePeaks}
setActiveAudioID={(id, context) => setActive({ id, context })}
- now={Date.now()}
onFirstPlayed={action('onFirstPlayed')}
activeAudioID={active.id}
activeAudioContext={active.context}
@@ -134,7 +133,6 @@ const createProps = (overrideProps: Partial = {}): Props => ({
getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined),
i18n,
id: text('id', overrideProps.id || ''),
- now: Date.now(),
renderingContext: 'storybook',
interactionMode: overrideProps.interactionMode || 'keyboard',
isSticker: isBoolean(overrideProps.isSticker)
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 7b19df5b5d..97778f6349 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -143,7 +143,6 @@ export type AudioAttachmentProps = {
expirationLength?: number;
expirationTimestamp?: number;
id: string;
- now: number;
played: boolean;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
@@ -240,7 +239,6 @@ export type PropsHousekeeping = {
disableScroll?: boolean;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
- now: number;
interactionMode: InteractionModeType;
item?: TimelineItemType;
nextItem?: TimelineItemType;
@@ -709,7 +707,6 @@ export class Message extends React.PureComponent {
isTapToViewExpired,
status,
i18n,
- now,
text,
textPending,
timestamp,
@@ -732,7 +729,6 @@ export class Message extends React.PureComponent {
isShowingImage={this.isShowingImage()}
isSticker={isStickerLike}
isTapToViewExpired={isTapToViewExpired}
- now={now}
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
showMessageDetail={showMessageDetail}
status={status}
@@ -786,7 +782,6 @@ export class Message extends React.PureComponent {
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
markViewed,
- now,
quote,
readStatus,
reducedMotion,
@@ -922,7 +917,6 @@ export class Message extends React.PureComponent {
expirationLength,
expirationTimestamp,
id,
- now,
played,
showMessageDetail,
status,
diff --git a/ts/components/conversation/MessageAudio.tsx b/ts/components/conversation/MessageAudio.tsx
index 5b987765e2..9bbfda5714 100644
--- a/ts/components/conversation/MessageAudio.tsx
+++ b/ts/components/conversation/MessageAudio.tsx
@@ -28,7 +28,6 @@ export type Props = {
expirationLength?: number;
expirationTimestamp?: number;
id: string;
- now: number;
played: boolean;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
@@ -160,7 +159,6 @@ export const MessageAudio: React.FC = (props: Props) => {
expirationLength,
expirationTimestamp,
id,
- now,
played,
showMessageDetail,
status,
@@ -543,7 +541,6 @@ export const MessageAudio: React.FC = (props: Props) => {
isShowingImage={false}
isSticker={false}
isTapToViewExpired={false}
- now={now}
showMessageDetail={showMessageDetail}
status={status}
textPending={textPending}
diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx
index 622261112f..19056150d3 100644
--- a/ts/components/conversation/MessageDetail.tsx
+++ b/ts/components/conversation/MessageDetail.tsx
@@ -23,8 +23,6 @@ import { SendStatus } from '../../messages/MessageSendState';
import { WidthBreakpoint } from '../_util';
import * as log from '../../logging/log';
import { formatDateTimeLong } from '../../util/timestamp';
-import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
-import { MINUTE } from '../../util/durations';
export type Contact = Pick<
ConversationType,
@@ -101,20 +99,15 @@ export type PropsReduxActions = Pick<
export type ExternalProps = PropsData & PropsBackboneActions;
export type Props = PropsData & PropsBackboneActions & PropsReduxActions;
-type State = { nowThatUpdatesEveryMinute: number };
-
const contactSortCollator = new Intl.Collator();
const _keyForError = (error: Error): string => {
return `${error.name}-${error.message}`;
};
-export class MessageDetail extends React.Component {
- override state = { nowThatUpdatesEveryMinute: Date.now() };
-
+export class MessageDetail extends React.Component {
private readonly focusRef = React.createRef();
private readonly messageContainerRef = React.createRef();
- private nowThatUpdatesEveryMinuteInterval?: ReturnType;
public override componentDidMount(): void {
// When this component is created, it's initially not part of the DOM, and then it's
@@ -124,14 +117,6 @@ export class MessageDetail extends React.Component {
this.focusRef.current.focus();
}
});
-
- this.nowThatUpdatesEveryMinuteInterval = setInterval(() => {
- this.setState({ nowThatUpdatesEveryMinute: Date.now() });
- }, MINUTE);
- }
-
- public override componentWillUnmount(): void {
- clearTimeoutIfNecessary(this.nowThatUpdatesEveryMinuteInterval);
}
public renderAvatar(contact: Contact): JSX.Element {
@@ -314,7 +299,6 @@ export class MessageDetail extends React.Component {
showVisualAttachment,
theme,
} = this.props;
- const { nowThatUpdatesEveryMinute } = this.state;
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
@@ -352,7 +336,6 @@ export class MessageDetail extends React.Component {
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
markViewed={markViewed}
messageExpanded={noop}
- now={nowThatUpdatesEveryMinute}
openConversation={openConversation}
openLink={openLink}
reactToMessage={reactToMessage}
diff --git a/ts/components/conversation/MessageMetadata.tsx b/ts/components/conversation/MessageMetadata.tsx
index 71dcfd0a68..9b3780652e 100644
--- a/ts/components/conversation/MessageMetadata.tsx
+++ b/ts/components/conversation/MessageMetadata.tsx
@@ -24,7 +24,6 @@ type PropsType = {
isShowingImage: boolean;
isSticker?: boolean;
isTapToViewExpired?: boolean;
- now: number;
onWidthMeasured?: (width: number) => unknown;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
@@ -44,7 +43,6 @@ export const MessageMetadata = ({
isShowingImage,
isSticker,
isTapToViewExpired,
- now,
onWidthMeasured,
showMessageDetail,
status,
@@ -106,7 +104,6 @@ export const MessageMetadata = ({
=> [
const createProps = (overrideProps: Partial = {}): Props => ({
i18n,
timestamp: overrideProps.timestamp || Date.now(),
- now: Date.now(),
module: text('module', ''),
withImageNoCaption: boolean('withImageNoCaption', false),
withSticker: boolean('withSticker', false),
diff --git a/ts/components/conversation/MessageTimestamp.tsx b/ts/components/conversation/MessageTimestamp.tsx
index 219b8819b9..c151a34566 100644
--- a/ts/components/conversation/MessageTimestamp.tsx
+++ b/ts/components/conversation/MessageTimestamp.tsx
@@ -9,9 +9,9 @@ import { formatTime } from '../../util/timestamp';
import type { LocalizerType } from '../../types/Util';
import { Time } from '../Time';
+import { useNowThatUpdatesEveryMinute } from '../../hooks/useNowThatUpdatesEveryMinute';
export type Props = {
- now: number;
timestamp: number;
module?: string;
withImageNoCaption?: boolean;
@@ -25,12 +25,12 @@ export function MessageTimestamp({
direction,
i18n,
module,
- now,
timestamp,
withImageNoCaption,
withSticker,
withTapToViewExpired,
}: Readonly): ReactElement {
+ const now = useNowThatUpdatesEveryMinute();
const moduleName = module || 'module-timestamp';
return (
diff --git a/ts/components/conversation/Quote.stories.tsx b/ts/components/conversation/Quote.stories.tsx
index a578586202..37074e7f60 100644
--- a/ts/components/conversation/Quote.stories.tsx
+++ b/ts/components/conversation/Quote.stories.tsx
@@ -61,7 +61,6 @@ const defaultMessageProps: MessagesProps = {
getPreferredBadge: () => undefined,
i18n,
id: 'messageId',
- now: Date.now(),
renderingContext: 'storybook',
interactionMode: 'keyboard',
isBlocked: false,
diff --git a/ts/components/conversation/Timeline.stories.tsx b/ts/components/conversation/Timeline.stories.tsx
index 327e50da0c..6de37f212f 100644
--- a/ts/components/conversation/Timeline.stories.tsx
+++ b/ts/components/conversation/Timeline.stories.tsx
@@ -414,13 +414,11 @@ const renderItem = ({
containerElementRef,
containerWidthBreakpoint,
isOldestTimelineItem,
- now,
}: {
messageId: string;
containerElementRef: React.RefObject;
containerWidthBreakpoint: WidthBreakpoint;
isOldestTimelineItem: boolean;
- now: number;
}) => (
undefined}
@@ -432,7 +430,6 @@ const renderItem = ({
item={items[messageId]}
previousItem={undefined}
nextItem={undefined}
- now={now}
i18n={i18n}
interactionMode="keyboard"
theme={ThemeType.light}
diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx
index fa21d64c49..ef8a9067e3 100644
--- a/ts/components/conversation/Timeline.tsx
+++ b/ts/components/conversation/Timeline.tsx
@@ -41,7 +41,6 @@ import {
scrollToBottom,
setScrollBottom,
} from '../../util/scrollUtil';
-import { MINUTE } from '../../util/durations';
const AT_BOTTOM_THRESHOLD = 15;
const MIN_ROW_HEIGHT = 18;
@@ -118,7 +117,6 @@ type PropsHousekeepingType = {
isOldestTimelineItem: boolean;
messageId: string;
nextMessageId: undefined | string;
- now: number;
previousMessageId: undefined | string;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
}) => JSX.Element;
@@ -175,7 +173,6 @@ type StateType = {
hasRecentlyScrolled: boolean;
lastMeasuredWarningHeight: number;
newestFullyVisibleMessageId?: string;
- nowThatUpdatesEveryMinute: number;
oldestPartiallyVisibleMessageId?: string;
widthBreakpoint: WidthBreakpoint;
};
@@ -269,12 +266,10 @@ export class Timeline extends React.Component<
private hasRecentlyScrolledTimeout?: NodeJS.Timeout;
private delayedPeekTimeout?: NodeJS.Timeout;
- private nowThatUpdatesEveryMinuteInterval?: NodeJS.Timeout;
override state: StateType = {
hasRecentlyScrolled: true,
hasDismissedDirectContactSpoofingWarning: false,
- nowThatUpdatesEveryMinute: Date.now(),
// These may be swiftly overridden.
lastMeasuredWarningHeight: 0,
@@ -512,17 +507,10 @@ export class Timeline extends React.Component<
const { id, peekGroupCallForTheFirstTime } = this.props;
peekGroupCallForTheFirstTime(id);
}, 500);
-
- this.nowThatUpdatesEveryMinuteInterval = setInterval(() => {
- this.setState({ nowThatUpdatesEveryMinute: Date.now() });
- }, MINUTE);
}
public override componentWillUnmount(): void {
- const {
- delayedPeekTimeout,
- nowThatUpdatesEveryMinuteInterval: nowThatUpdatesEveryMinuteTimeout,
- } = this;
+ const { delayedPeekTimeout } = this;
window.unregisterForActive(this.markNewestFullyVisibleMessageRead);
@@ -530,7 +518,6 @@ export class Timeline extends React.Component<
this.intersectionObserver?.disconnect();
clearTimeoutIfNecessary(delayedPeekTimeout);
- clearTimeoutIfNecessary(nowThatUpdatesEveryMinuteTimeout);
}
public override getSnapshotBeforeUpdate(
@@ -774,7 +761,6 @@ export class Timeline extends React.Component<
hasRecentlyScrolled,
lastMeasuredWarningHeight,
newestFullyVisibleMessageId,
- nowThatUpdatesEveryMinute,
oldestPartiallyVisibleMessageId,
widthBreakpoint,
} = this.state;
@@ -883,7 +869,6 @@ export class Timeline extends React.Component<
isOldestTimelineItem: haveOldest && itemIndex === 0,
messageId,
nextMessageId,
- now: nowThatUpdatesEveryMinute,
previousMessageId,
unreadIndicatorPlacement,
})}
diff --git a/ts/components/conversation/TimelineItem.tsx b/ts/components/conversation/TimelineItem.tsx
index 29b2faa101..1575835fbc 100644
--- a/ts/components/conversation/TimelineItem.tsx
+++ b/ts/components/conversation/TimelineItem.tsx
@@ -155,7 +155,6 @@ type PropsLocalType = {
theme: ThemeType;
previousItem: undefined | TimelineItemType;
nextItem: undefined | TimelineItemType;
- now: number;
unreadIndicatorPlacement?: undefined | UnreadIndicatorPlacement;
};
@@ -190,7 +189,6 @@ export class TimelineItem extends React.PureComponent {
i18n,
theme,
nextItem,
- now,
previousItem,
renderContact,
renderUniversalTimerNotification,
@@ -237,7 +235,6 @@ export class TimelineItem extends React.PureComponent {
conversationId={conversationId}
i18n={i18n}
nextItem={nextItem}
- now={now}
returnToActiveCall={returnToActiveCall}
startCallingLobby={startCallingLobby}
{...item.data}
diff --git a/ts/hooks/useNowThatUpdatesEveryMinute.ts b/ts/hooks/useNowThatUpdatesEveryMinute.ts
new file mode 100644
index 0000000000..ca7c0eb4c3
--- /dev/null
+++ b/ts/hooks/useNowThatUpdatesEveryMinute.ts
@@ -0,0 +1,27 @@
+// Copyright 2022 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { EventEmitter } from 'events';
+import { useEffect, useState } from 'react';
+
+import { MINUTE } from '../util/durations';
+
+const ev = new EventEmitter();
+setInterval(() => ev.emit('tick'), MINUTE);
+
+export function useNowThatUpdatesEveryMinute(): number {
+ const [now, setNow] = useState(Date.now());
+
+ useEffect(() => {
+ const updateNow = () => setNow(Date.now());
+ updateNow();
+
+ ev.on('tick', updateNow);
+
+ return () => {
+ ev.off('tick', updateNow);
+ };
+ }, []);
+
+ return now;
+}
diff --git a/ts/state/smart/MessageAudio.tsx b/ts/state/smart/MessageAudio.tsx
index ec38d5607b..be6c44d312 100644
--- a/ts/state/smart/MessageAudio.tsx
+++ b/ts/state/smart/MessageAudio.tsx
@@ -29,7 +29,6 @@ export type Props = {
expirationLength?: number;
expirationTimestamp?: number;
id: string;
- now: number;
played: boolean;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
diff --git a/ts/state/smart/Timeline.tsx b/ts/state/smart/Timeline.tsx
index 98bcfb9b00..54458e3273 100644
--- a/ts/state/smart/Timeline.tsx
+++ b/ts/state/smart/Timeline.tsx
@@ -108,7 +108,6 @@ function renderItem({
isOldestTimelineItem,
messageId,
nextMessageId,
- now,
previousMessageId,
unreadIndicatorPlacement,
}: {
@@ -119,7 +118,6 @@ function renderItem({
isOldestTimelineItem: boolean;
messageId: string;
nextMessageId: undefined | string;
- now: number;
previousMessageId: undefined | string;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
}): JSX.Element {
@@ -133,7 +131,6 @@ function renderItem({
messageId={messageId}
previousMessageId={previousMessageId}
nextMessageId={nextMessageId}
- now={now}
renderEmojiPicker={renderEmojiPicker}
renderReactionPicker={renderReactionPicker}
renderAudioAttachment={renderAudioAttachment}
diff --git a/ts/state/smart/TimelineItem.tsx b/ts/state/smart/TimelineItem.tsx
index 74e512bd88..72e6671a6b 100644
--- a/ts/state/smart/TimelineItem.tsx
+++ b/ts/state/smart/TimelineItem.tsx
@@ -28,7 +28,6 @@ type ExternalProps = {
messageId: string;
nextMessageId: undefined | string;
previousMessageId: undefined | string;
- now: number;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
};
@@ -48,7 +47,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
messageId,
nextMessageId,
previousMessageId,
- now,
unreadIndicatorPlacement,
} = props;
@@ -71,7 +69,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
item,
previousItem,
nextItem,
- now,
id: messageId,
containerElementRef,
conversationId,