From c225b8ec293a6e6e36eef14b7be7bc31cd733141 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 27 Mar 2023 08:01:09 +0100
Subject: [PATCH] Conform more code to `strictNullChecks` (#10444

* Conform more code to `strictNullChecks`

* Fix tests

* Fix tests
---
 src/components/structures/MessagePanel.tsx    | 14 ++++------
 src/components/structures/RoomView.tsx        | 26 +++++++++----------
 src/components/structures/ThreadView.tsx      | 14 +++++-----
 src/components/structures/TimelinePanel.tsx   |  2 +-
 .../views/dialogs/BetaFeedbackDialog.tsx      |  1 +
 .../views/dialogs/BulkRedactDialog.tsx        |  4 +--
 .../views/dialogs/DevtoolsDialog.tsx          |  2 +-
 .../views/dialogs/ForwardDialog.tsx           |  5 ++--
 .../dialogs/RegistrationEmailPromptDialog.tsx |  1 +
 .../views/dialogs/ServerPickerDialog.tsx      |  2 +-
 src/components/views/dialogs/TermsDialog.tsx  |  8 +++---
 .../views/dialogs/TextInputDialog.tsx         |  2 +-
 .../spotlight/RoomResultContextMenus.tsx      |  4 +--
 .../dialogs/spotlight/SpotlightDialog.tsx     |  2 +-
 .../views/elements/EditableText.tsx           | 20 +++++++-------
 .../views/elements/EventListSummary.tsx       |  6 ++---
 src/components/views/elements/ReplyChain.tsx  |  7 ++---
 .../views/messages/EditHistoryMessage.tsx     |  2 +-
 .../views/right_panel/PinnedMessagesCard.tsx  |  2 +-
 src/components/views/right_panel/UserInfo.tsx |  6 ++---
 src/components/views/rooms/EmojiButton.tsx    |  2 +-
 .../views/rooms/MessageComposerButtons.tsx    |  4 +--
 src/hooks/usePermalinkEvent.ts                |  4 +--
 src/hooks/usePermalinkTargetRoom.ts           |  2 +-
 src/toasts/AnalyticsToast.tsx                 |  3 +--
 src/utils/SortMembers.ts                      |  5 +++-
 .../structures/MessagePanel-test.tsx          |  2 +-
 .../views/dialogs/ForwardDialog-test.tsx      |  2 ++
 .../views/right_panel/UserInfo-test.tsx       |  6 ++++-
 29 files changed, 85 insertions(+), 75 deletions(-)

diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx
index 650313b967..37b733f895 100644
--- a/src/components/structures/MessagePanel.tsx
+++ b/src/components/structures/MessagePanel.tsx
@@ -24,8 +24,6 @@ import { logger } from "matrix-js-sdk/src/logger";
 import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
 import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
 import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
-import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
-import { ListenerMap } from "matrix-js-sdk/src/models/typed-event-emitter";
 
 import shouldHideEvent from "../../shouldHideEvent";
 import { wantsDateSeparator } from "../../DateUtils";
@@ -543,7 +541,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
         return null;
     }
 
-    private collectGhostReadMarker = (node: HTMLElement): void => {
+    private collectGhostReadMarker = (node: HTMLElement | null): void => {
         if (node) {
             // now the element has appeared, change the style which will trigger the CSS transition
             requestAnimationFrame(() => {
@@ -788,13 +786,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
                 continuation={continuation}
                 isRedacted={mxEv.isRedacted()}
                 replacingEventId={mxEv.replacingEventId()}
-                editState={isEditing && this.props.editState}
+                editState={isEditing ? this.props.editState : undefined}
                 onHeightChanged={this.onHeightChanged}
                 readReceipts={readReceipts}
                 readReceiptMap={this.readReceiptMap}
                 showUrlPreview={this.props.showUrlPreview}
                 checkUnmounting={this.isUnmounting}
-                eventSendStatus={mxEv.getAssociatedStatus()}
+                eventSendStatus={mxEv.getAssociatedStatus() ?? undefined}
                 isTwelveHour={this.props.isTwelveHour}
                 permalinkCreator={this.props.permalinkCreator}
                 last={last}
@@ -836,9 +834,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
             return null;
         }
 
-        const receiptDestination: ReadReceipt<string, ListenerMap<string>> = this.context.threadId
-            ? room.getThread(this.context.threadId)
-            : room;
+        const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room;
 
         const receipts: IReadReceiptProps[] = [];
 
@@ -1215,7 +1211,7 @@ class CreationGrouper extends BaseGrouper {
 
         let summaryText: string;
         const roomId = ev.getRoomId();
-        const creator = ev.sender ? ev.sender.name : ev.getSender();
+        const creator = ev.sender?.name ?? ev.getSender();
         if (DMRoomMap.shared().getUserIdForRoomId(roomId)) {
             summaryText = _t("%(creator)s created this DM.", { creator });
         } else {
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index ccb22253af..9af69100ab 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -377,7 +377,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
 
     private roomView = createRef<HTMLElement>();
     private searchResultsPanel = createRef<ScrollPanel>();
-    private messagePanel?: TimelinePanel;
+    private messagePanel: TimelinePanel | null = null;
     private roomViewBody = createRef<HTMLDivElement>();
 
     public static contextType = SDKContext;
@@ -611,11 +611,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
 
         const newState: Partial<IRoomState> = {
             roomId: roomId ?? undefined,
-            roomAlias: this.context.roomViewStore.getRoomAlias(),
+            roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined,
             roomLoading: this.context.roomViewStore.isRoomLoading(),
-            roomLoadError: this.context.roomViewStore.getRoomLoadError(),
+            roomLoadError: this.context.roomViewStore.getRoomLoadError() ?? undefined,
             joining: this.context.roomViewStore.isJoining(),
-            replyToEvent: this.context.roomViewStore.getQuotingEvent(),
+            replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined,
             // we should only peek once we have a ready client
             shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(),
             showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
@@ -654,7 +654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
             // and the root event.
             // The rest will be lost for now, until the aggregation API on the server
             // becomes available to fetch a whole thread
-            if (!initialEvent) {
+            if (!initialEvent && this.context.client) {
                 initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined;
             }
 
@@ -848,7 +848,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                     isPeeking: true, // this will change to false if peeking fails
                 });
                 this.context.client
-                    .peekInRoom(roomId)
+                    ?.peekInRoom(roomId)
                     .then((room) => {
                         if (this.unmounted) {
                             return;
@@ -883,7 +883,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                     });
             } else if (room) {
                 // Stop peeking because we have joined this room previously
-                this.context.client.stopPeeking();
+                this.context.client?.stopPeeking();
                 this.setState({ isPeeking: false });
             }
         }
@@ -909,9 +909,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         this.onRoomViewStoreUpdate(true);
 
         const call = this.getCallForRoom();
-        const callState = call ? call.state : null;
+        const callState = call?.state;
         this.setState({
-            callState: callState,
+            callState,
         });
 
         this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
@@ -959,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         }
 
         if (this.state.shouldPeek) {
-            this.context.client.stopPeeking();
+            this.context.client?.stopPeeking();
         }
 
         // stop tracking room changes to format permalinks
@@ -1010,7 +1010,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
 
         if (this.viewsLocalRoom) {
             // clean up if this was a local room
-            this.context.client.store.removeRoom(this.state.room.roomId);
+            this.context.client?.store.removeRoom(this.state.room.roomId);
         }
     }
 
@@ -1469,7 +1469,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
 
     private updatePermissions(room: Room): void {
         if (room) {
-            const me = this.context.client.getUserId();
+            const me = this.context.client.getSafeUserId();
             const canReact =
                 room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me);
             const canSendMessages = room.maySendMessage();
@@ -1866,7 +1866,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
 
     // this has to be a proper method rather than an unnamed function,
     // otherwise react calls it with null on each update.
-    private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
+    private gatherTimelinePanelRef = (r: TimelinePanel | null): void => {
         this.messagePanel = r;
     };
 
diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx
index 8eaf3f2be9..346883f640 100644
--- a/src/components/structures/ThreadView.tsx
+++ b/src/components/structures/ThreadView.tsx
@@ -171,7 +171,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
                 if (payload.event && !payload.event.getThread()) return;
                 this.setState(
                     {
-                        editState: payload.event ? new EditorStateTransfer(payload.event) : null,
+                        editState: payload.event ? new EditorStateTransfer(payload.event) : undefined,
                     },
                     () => {
                         if (payload.event) {
@@ -213,9 +213,11 @@ export default class ThreadView extends React.Component<IProps, IState> {
     };
 
     private get threadLastReply(): MatrixEvent | undefined {
-        return this.state.thread?.lastReply((ev: MatrixEvent) => {
-            return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status;
-        });
+        return (
+            this.state.thread?.lastReply((ev: MatrixEvent) => {
+                return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status;
+            }) ?? undefined
+        );
     }
 
     private updateThread = (thread?: Thread): void => {
@@ -245,7 +247,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
 
     private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void {
         if (oldThread) {
-            this.state.thread.off(ThreadEvent.NewReply, this.updateThreadRelation);
+            this.state.thread?.off(ThreadEvent.NewReply, this.updateThreadRelation);
             this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation);
         }
         if (thread) {
@@ -344,7 +346,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
     };
 
     public render(): React.ReactNode {
-        const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null;
+        const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : undefined;
 
         const threadRelation = this.threadRelation;
 
diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx
index 719bba71df..b496454c7e 100644
--- a/src/components/structures/TimelinePanel.tsx
+++ b/src/components/structures/TimelinePanel.tsx
@@ -79,7 +79,7 @@ interface IProps {
     // representing.  This may or may not have a room, depending on what it's
     // a timeline representing.  If it has a room, we maintain RRs etc for
     // that room.
-    timelineSet?: EventTimelineSet;
+    timelineSet: EventTimelineSet;
     // overlay events from a second timelineset on the main timeline
     // added to support virtual rooms
     // events from the overlay timeline set will be added by localTimestamp
diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx
index 8223fde929..a262fd4f5e 100644
--- a/src/components/views/dialogs/BetaFeedbackDialog.tsx
+++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx
@@ -33,6 +33,7 @@ interface IProps {
 
 const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
     const info = SettingsStore.getBetaInfo(featureId);
+    if (!info) return null;
 
     return (
         <GenericFeatureFeedbackDialog
diff --git a/src/components/views/dialogs/BulkRedactDialog.tsx b/src/components/views/dialogs/BulkRedactDialog.tsx
index ba12f21039..87dbcfda07 100644
--- a/src/components/views/dialogs/BulkRedactDialog.tsx
+++ b/src/components/views/dialogs/BulkRedactDialog.tsx
@@ -42,7 +42,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
     const { matrixClient: cli, room, member, onFinished } = props;
     const [keepStateEvents, setKeepStateEvents] = useState(true);
 
-    let timeline = room.getLiveTimeline();
+    let timeline: EventTimeline | null = room.getLiveTimeline();
     let eventsToRedact: MatrixEvent[] = [];
     while (timeline) {
         eventsToRedact = [
@@ -93,7 +93,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
             await Promise.all(
                 eventsToRedact.reverse().map(async (event): Promise<void> => {
                     try {
-                        await cli.redactEvent(room.roomId, event.getId());
+                        await cli.redactEvent(room.roomId, event.getId()!);
                     } catch (err) {
                         // log and swallow errors
                         logger.error("Could not redact", event.getId());
diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 3fd45c17a1..655b175edf 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -71,7 +71,7 @@ interface IProps {
 type ToolInfo = [label: string, tool: Tool];
 
 const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
-    const [tool, setTool] = useState<ToolInfo>(null);
+    const [tool, setTool] = useState<ToolInfo | null>(null);
 
     let body: JSX.Element;
     let onBack: () => void;
diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx
index af758507d2..75af82d86a 100644
--- a/src/components/views/dialogs/ForwardDialog.tsx
+++ b/src/components/views/dialogs/ForwardDialog.tsx
@@ -51,6 +51,7 @@ import { ButtonEvent } from "../elements/AccessibleButton";
 import { isLocationEvent } from "../../../utils/EventUtils";
 import { isSelfLocation, locationEventGeoUri } from "../../../utils/location";
 import { RoomContextDetails } from "../rooms/RoomContextDetails";
+import { filterBoolean } from "../../../utils/arrays";
 
 const AVATAR_SIZE = 30;
 
@@ -194,7 +195,7 @@ const transformEvent = (event: MatrixEvent): { type: string; content: IContent }
 };
 
 const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => {
-    const userId = cli.getUserId();
+    const userId = cli.getSafeUserId();
     const [profileInfo, setProfileInfo] = useState<any>({});
     useEffect(() => {
         cli.getProfileInfo(userId).then((info) => setProfileInfo(info));
@@ -242,7 +243,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
     if (lcQuery) {
         rooms = new QueryMatcher<Room>(rooms, {
             keys: ["name"],
-            funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
+            funcs: [(r) => filterBoolean([r.getCanonicalAlias(), ...r.getAltAliases()])],
             shouldMatchWordsOnly: false,
         }).match(lcQuery);
     }
diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
index a175e14606..9d8adc9d99 100644
--- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
+++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
@@ -34,6 +34,7 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
 
     const onSubmit = async (e: SyntheticEvent): Promise<void> => {
         e.preventDefault();
+        if (!fieldRef.current) return;
         if (email) {
             const valid = await fieldRef.current.validate({});
 
diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx
index 75436e03a8..2b208b29f3 100644
--- a/src/components/views/dialogs/ServerPickerDialog.tsx
+++ b/src/components/views/dialogs/ServerPickerDialog.tsx
@@ -141,7 +141,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
                     return !error;
                 },
                 invalid: function ({ error }) {
-                    return error;
+                    return error ?? null;
                 },
             },
         ],
diff --git a/src/components/views/dialogs/TermsDialog.tsx b/src/components/views/dialogs/TermsDialog.tsx
index 57efd54b99..b0835c3afc 100644
--- a/src/components/views/dialogs/TermsDialog.tsx
+++ b/src/components/views/dialogs/TermsDialog.tsx
@@ -49,7 +49,7 @@ interface ITermsDialogProps {
     /**
      * urls that the user has already agreed to
      */
-    agreedUrls?: string[];
+    agreedUrls: string[];
 
     /**
      * Called with:
@@ -127,7 +127,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
     };
 
     public render(): React.ReactNode {
-        const rows = [];
+        const rows: JSX.Element[] = [];
         for (const policiesAndService of this.props.policiesAndServicePairs) {
             const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl);
 
@@ -135,8 +135,8 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
             for (let i = 0; i < policyValues.length; ++i) {
                 const termDoc = policyValues[i];
                 const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version"));
-                let serviceName;
-                let summary;
+                let serviceName: JSX.Element | undefined;
+                let summary: JSX.Element | undefined;
                 if (i === 0) {
                     serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
                     summary = this.summaryForServiceType(policiesAndService.service.serviceType);
diff --git a/src/components/views/dialogs/TextInputDialog.tsx b/src/components/views/dialogs/TextInputDialog.tsx
index 9c22e8b2e3..ed9f377508 100644
--- a/src/components/views/dialogs/TextInputDialog.tsx
+++ b/src/components/views/dialogs/TextInputDialog.tsx
@@ -100,7 +100,7 @@ export default class TextInputDialog extends React.Component<IProps, IState> {
     };
 
     private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
-        const result = await this.props.validator(fieldState);
+        const result = await this.props.validator!(fieldState);
         this.setState({
             valid: !!result.valid,
         });
diff --git a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx
index 8d95860ef8..096138e056 100644
--- a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx
+++ b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx
@@ -38,7 +38,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
     const [generalMenuPosition, setGeneralMenuPosition] = useState<DOMRect | null>(null);
     const [notificationMenuPosition, setNotificationMenuPosition] = useState<DOMRect | null>(null);
 
-    let generalMenu: JSX.Element;
+    let generalMenu: JSX.Element | undefined;
     if (generalMenuPosition !== null) {
         if (room.isSpaceRoom()) {
             generalMenu = (
@@ -59,7 +59,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
         }
     }
 
-    let notificationMenu: JSX.Element;
+    let notificationMenu: JSX.Element | undefined;
     if (notificationMenuPosition !== null) {
         notificationMenu = (
             <RoomNotificationContextMenu
diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx
index 6e2b113ee6..37c2bace25 100644
--- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx
+++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx
@@ -440,7 +440,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
 
         // Sort results by most recent activity
 
-        const myUserId = cli.getUserId();
+        const myUserId = cli.getSafeUserId();
         for (const resultArray of Object.values(results)) {
             resultArray.sort((a: Result, b: Result) => {
                 if (isRoomResult(a) || isRoomResult(b)) {
diff --git a/src/components/views/elements/EditableText.tsx b/src/components/views/elements/EditableText.tsx
index b4695d552b..5c11e52ecc 100644
--- a/src/components/views/elements/EditableText.tsx
+++ b/src/components/views/elements/EditableText.tsx
@@ -27,17 +27,17 @@ enum Phases {
 
 interface IProps {
     onValueChanged?: (value: string, shouldSubmit: boolean) => void;
-    initialValue?: string;
-    label?: string;
-    placeholder?: string;
-    className?: string;
+    initialValue: string;
+    label: string;
+    placeholder: string;
+    className: string;
     labelClassName?: string;
-    placeholderClassName?: string;
+    placeholderClassName: string;
     // Overrides blurToSubmit if true
     blurToCancel?: boolean;
     // Will cause onValueChanged(value, true) to fire on blur
-    blurToSubmit?: boolean;
-    editable?: boolean;
+    blurToSubmit: boolean;
+    editable: boolean;
 }
 
 interface IState {
@@ -109,11 +109,11 @@ export default class EditableText extends React.Component<IProps, IState> {
         this.value = this.props.initialValue;
         this.showPlaceholder(!this.value);
         this.onValueChanged(false);
-        this.editableDiv.current.blur();
+        this.editableDiv.current?.blur();
     };
 
     private onValueChanged = (shouldSubmit: boolean): void => {
-        this.props.onValueChanged(this.value, shouldSubmit);
+        this.props.onValueChanged?.(this.value, shouldSubmit);
     };
 
     private onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>): void => {
@@ -171,7 +171,7 @@ export default class EditableText extends React.Component<IProps, IState> {
 
     private onFinish = (
         ev: React.KeyboardEvent<HTMLDivElement> | React.FocusEvent<HTMLDivElement>,
-        shouldSubmit?: boolean,
+        shouldSubmit = false,
     ): void => {
         // eslint-disable-next-line @typescript-eslint/no-this-alias
         const self = this;
diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx
index 5498773777..882e68c9f8 100644
--- a/src/components/views/elements/EventListSummary.tsx
+++ b/src/components/views/elements/EventListSummary.tsx
@@ -187,8 +187,8 @@ export default class EventListSummary extends React.Component<IProps> {
 
             let transition = t;
 
-            if (i < transitions.length - 1 && modMap[t] && modMap[t].after === t2) {
-                transition = modMap[t].newTransition;
+            if (i < transitions.length - 1 && modMap[t] && modMap[t]!.after === t2) {
+                transition = modMap[t]!.newTransition;
                 i++;
             }
 
@@ -380,7 +380,7 @@ export default class EventListSummary extends React.Component<IProps> {
         return res ?? null;
     }
 
-    private static getTransitionSequence(events: IUserEvents[]): TransitionType[] {
+    private static getTransitionSequence(events: IUserEvents[]): Array<TransitionType | null> {
         return events.map(EventListSummary.getTransition);
     }
 
diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx
index be415027c4..e4a720ce4c 100644
--- a/src/components/views/elements/ReplyChain.tsx
+++ b/src/components/views/elements/ReplyChain.tsx
@@ -148,6 +148,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
     private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent | null> {
         try {
             const inReplyToEventId = getParentEventId(ev);
+            if (!inReplyToEventId) return null;
             return await this.getEvent(inReplyToEventId);
         } catch (e) {
             return null;
@@ -196,7 +197,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
     };
 
     private getReplyChainColorClass(ev: MatrixEvent): string {
-        return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyChain");
+        return getUserNameColorClass(ev.getSender()!).replace("Username", "ReplyChain");
     }
 
     public render(): React.ReactNode {
@@ -231,8 +232,8 @@ export default class ReplyChain extends React.Component<IProps, IState> {
                             pill: (
                                 <Pill
                                     type={PillType.UserMention}
-                                    room={room}
-                                    url={makeUserPermalink(ev.getSender())}
+                                    room={room ?? undefined}
+                                    url={makeUserPermalink(ev.getSender()!)}
                                     shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
                                 />
                             ),
diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx
index 906867d6a8..312e694786 100644
--- a/src/components/views/messages/EditHistoryMessage.tsx
+++ b/src/components/views/messages/EditHistoryMessage.tsx
@@ -63,7 +63,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
         const event = this.props.mxEvent;
         const room = cli.getRoom(event.getRoomId());
         event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
-        const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
+        const canRedact = room?.currentState.maySendRedactionForEvent(event, userId) ?? false;
         this.state = { canRedact, sendStatus: event.getAssociatedStatus() };
     }
 
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index aa4434427c..ca1bf2ed9d 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -71,7 +71,7 @@ export const usePinnedEvents = (room?: Room): string[] => {
 };
 
 function getReadPinnedEventIds(room?: Room): Set<string> {
-    return new Set(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []);
+    return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []);
 }
 
 export const useReadPinnedEvents = (room?: Room): Set<string> => {
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index a3dd4cdd39..8519fb86e3 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -448,7 +448,7 @@ export const UserOptionsSection: React.FC<{
                     const inviter = new MultiInviter(roomId || "");
                     await inviter.invite([member.userId]).then(() => {
                         if (inviter.getCompletionState(member.userId) !== "invited") {
-                            throw new Error(inviter.getErrorText(member.userId));
+                            throw new Error(inviter.getErrorText(member.userId) ?? undefined);
                         }
                     });
                 } catch (err) {
@@ -766,8 +766,8 @@ export const BanToggleButton = ({
                               const myMember = child.getMember(cli.credentials.userId || "");
                               const theirMember = child.getMember(member.userId);
                               return (
-                                  myMember &&
-                                  theirMember &&
+                                  !!myMember &&
+                                  !!theirMember &&
                                   theirMember.membership !== "ban" &&
                                   myMember.powerLevel > theirMember.powerLevel &&
                                   child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel)
diff --git a/src/components/views/rooms/EmojiButton.tsx b/src/components/views/rooms/EmojiButton.tsx
index 013feeee24..db7accb62c 100644
--- a/src/components/views/rooms/EmojiButton.tsx
+++ b/src/components/views/rooms/EmojiButton.tsx
@@ -25,7 +25,7 @@ import { OverflowMenuContext } from "./MessageComposerButtons";
 
 interface IEmojiButtonProps {
     addEmoji: (unicode: string) => boolean;
-    menuPosition: MenuProps;
+    menuPosition?: MenuProps;
     className?: string;
 }
 
diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx
index 30856cabc7..abcb82ee63 100644
--- a/src/components/views/rooms/MessageComposerButtons.tsx
+++ b/src/components/views/rooms/MessageComposerButtons.tsx
@@ -73,8 +73,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
         return null;
     }
 
-    let mainButtons: ReactElement[];
-    let moreButtons: ReactElement[];
+    let mainButtons: ReactNode[];
+    let moreButtons: ReactNode[];
     if (narrow) {
         mainButtons = [
             isWysiwygLabEnabled ? (
diff --git a/src/hooks/usePermalinkEvent.ts b/src/hooks/usePermalinkEvent.ts
index 39892c7958..1cb2b05e3b 100644
--- a/src/hooks/usePermalinkEvent.ts
+++ b/src/hooks/usePermalinkEvent.ts
@@ -75,8 +75,8 @@ export const usePermalinkEvent = (
         const fetchRoomEvent = async (): Promise<void> => {
             try {
                 const eventData = await MatrixClientPeg.get().fetchRoomEvent(
-                    parseResult.roomIdOrAlias,
-                    parseResult.eventId,
+                    parseResult.roomIdOrAlias!,
+                    parseResult.eventId!,
                 );
                 setEvent(new MatrixEvent(eventData));
             } catch {}
diff --git a/src/hooks/usePermalinkTargetRoom.ts b/src/hooks/usePermalinkTargetRoom.ts
index b9f6dd6dec..826f995bbe 100644
--- a/src/hooks/usePermalinkTargetRoom.ts
+++ b/src/hooks/usePermalinkTargetRoom.ts
@@ -83,7 +83,7 @@ const findRoom = (roomIdOrAlias: string): Room | null => {
 export const usePermalinkTargetRoom = (
     type: PillType | null,
     parseResult: PermalinkParts | null,
-    permalinkRoom: Room | null,
+    permalinkRoom: Room | undefined,
 ): Room | null => {
     // The listed permalink types require a room.
     // If it cannot be initially determined, it will be looked up later by a memo hook.
diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx
index d13d489e00..0a5b00cd58 100644
--- a/src/toasts/AnalyticsToast.tsx
+++ b/src/toasts/AnalyticsToast.tsx
@@ -15,7 +15,6 @@ limitations under the License.
 */
 
 import React from "react";
-import { Optional } from "matrix-events-sdk";
 
 import { _t } from "../languageHandler";
 import SdkConfig from "../SdkConfig";
@@ -75,7 +74,7 @@ const onLearnMorePreviouslyOptedIn = (): void => {
 
 const TOAST_KEY = "analytics";
 
-export function getPolicyUrl(): Optional<string> {
+export function getPolicyUrl(): string | undefined {
     return SdkConfig.get("privacy_policy_url");
 }
 
diff --git a/src/utils/SortMembers.ts b/src/utils/SortMembers.ts
index 2f63dacd7b..d19b461b1e 100644
--- a/src/utils/SortMembers.ts
+++ b/src/utils/SortMembers.ts
@@ -22,7 +22,10 @@ import { Member } from "./direct-messages";
 import DMRoomMap from "./DMRoomMap";
 
 export const compareMembers =
-    (activityScores: Record<string, IActivityScore>, memberScores: Record<string, IMemberScore>) =>
+    (
+        activityScores: Record<string, IActivityScore | undefined>,
+        memberScores: Record<string, IMemberScore | undefined>,
+    ) =>
     (a: Member | RoomMember, b: Member | RoomMember): number => {
         const aActivityScore = activityScores[a.userId]?.score ?? 0;
         const aMemberScore = memberScores[a.userId]?.score ?? 0;
diff --git a/test/components/structures/MessagePanel-test.tsx b/test/components/structures/MessagePanel-test.tsx
index 79d6ce963c..e4239533f1 100644
--- a/test/components/structures/MessagePanel-test.tsx
+++ b/test/components/structures/MessagePanel-test.tsx
@@ -716,7 +716,7 @@ describe("MessagePanel", function () {
         // Increase the length of the loop here to test performance issues with
         // rendering
 
-        const events = [];
+        const events: MatrixEvent[] = [];
         for (let i = 0; i < 100; i++) {
             events.push(
                 TestUtilsMatrix.mkMembership({
diff --git a/test/components/views/dialogs/ForwardDialog-test.tsx b/test/components/views/dialogs/ForwardDialog-test.tsx
index 748c0b193d..036e98b038 100644
--- a/test/components/views/dialogs/ForwardDialog-test.tsx
+++ b/test/components/views/dialogs/ForwardDialog-test.tsx
@@ -54,6 +54,7 @@ describe("ForwardDialog", () => {
     });
     const mockClient = getMockClientWithEventEmitter({
         getUserId: jest.fn().mockReturnValue(aliceId),
+        getSafeUserId: jest.fn().mockReturnValue(aliceId),
         isGuest: jest.fn().mockReturnValue(false),
         getVisibleRooms: jest.fn().mockReturnValue([]),
         getRoom: jest.fn(),
@@ -92,6 +93,7 @@ describe("ForwardDialog", () => {
         DMRoomMap.makeShared();
         jest.clearAllMocks();
         mockClient.getUserId.mockReturnValue("@bob:example.org");
+        mockClient.getSafeUserId.mockReturnValue("@bob:example.org");
         mockClient.sendEvent.mockReset();
     });
 
diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx
index 7ca7daa8f2..68274d9bf8 100644
--- a/test/components/views/right_panel/UserInfo-test.tsx
+++ b/test/components/views/right_panel/UserInfo-test.tsx
@@ -112,6 +112,7 @@ const mockClient = mocked({
     setIgnoredUsers: jest.fn(),
     isCryptoEnabled: jest.fn(),
     getUserId: jest.fn(),
+    getSafeUserId: jest.fn(),
     on: jest.fn(),
     off: jest.fn(),
     isSynapseAdministrator: jest.fn().mockResolvedValue(false),
@@ -348,6 +349,7 @@ describe("<DeviceItem />", () => {
     });
 
     it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", () => {
+        mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
         mockClient.getUserId.mockReturnValueOnce(defaultUserId);
         renderComponent();
 
@@ -431,6 +433,7 @@ describe("<UserOptionsSection />", () => {
     });
 
     it("does not show ignore or direct message buttons when member userId matches client userId", () => {
+        mockClient.getSafeUserId.mockReturnValueOnce(member.userId);
         mockClient.getUserId.mockReturnValueOnce(member.userId);
         renderComponent();
 
@@ -676,6 +679,7 @@ describe("<PowerLevelEditor />", () => {
             content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 },
         });
         mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent);
+        mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
         mockClient.getUserId.mockReturnValueOnce(defaultUserId);
         mockClient.setPowerLevel.mockResolvedValueOnce({ event_id: "123" });
         renderComponent();
@@ -879,7 +883,7 @@ describe("<BanToggleButton />", () => {
             },
         };
 
-        expect(callback(mockRoom)).toBe(null);
+        expect(callback(mockRoom)).toBe(false);
         expect(callback(mockRoom)).toBe(true);
     });