diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js
--- a/native/account/logged-out-modal.react.js
+++ b/native/account/logged-out-modal.react.js
@@ -270,7 +270,7 @@
   activeAlert = false;
 
   contentHeight: Value;
-  keyboardHeightValue = new Value(0);
+  keyboardHeightValue: Value = new Value(0);
   modeValue: Value;
 
   buttonOpacity: Value;
@@ -448,7 +448,7 @@
     this.props.dispatch({ type: resetUserStateActionType });
   }
 
-  hardwareBack = () => {
+  hardwareBack: () => boolean = () => {
     if (this.nextMode !== 'prompt') {
       this.goBackToPrompt();
       return true;
@@ -456,7 +456,7 @@
     return false;
   };
 
-  panelPaddingTop() {
+  panelPaddingTop(): Node {
     const headerHeight = Platform.OS === 'ios' ? 62.33 : 58.54;
     const promptButtonsSize = Platform.OS === 'ios' ? 40 : 61;
     const logInContainerSize = 140;
@@ -525,7 +525,7 @@
     ]);
   }
 
-  panelOpacity() {
+  panelOpacity(): Node {
     const targetPanelOpacity = isPastPrompt(this.modeValue);
 
     const panelOpacity = new Value(-1);
@@ -593,7 +593,7 @@
     Keyboard.dismiss();
   };
 
-  render() {
+  render(): React.Node {
     const { styles } = this.props;
 
     const siweButton = (
diff --git a/native/account/register-panel.react.js b/native/account/register-panel.react.js
--- a/native/account/register-panel.react.js
+++ b/native/account/register-panel.react.js
@@ -84,7 +84,7 @@
   confirmPasswordInput: ?TextInput;
   passwordBeingAutoFilled = false;
 
-  render() {
+  render(): React.Node {
     let confirmPasswordTextInputExtraProps;
     if (
       Platform.OS !== 'ios' ||
@@ -346,7 +346,7 @@
     );
   };
 
-  async registerAction(extraInfo: LogInExtraInfo) {
+  async registerAction(extraInfo: LogInExtraInfo): Promise<RegisterResult> {
     try {
       const result = await this.props.register({
         ...extraInfo,
diff --git a/native/account/registration/emoji-avatar-selection.react.js b/native/account/registration/emoji-avatar-selection.react.js
--- a/native/account/registration/emoji-avatar-selection.react.js
+++ b/native/account/registration/emoji-avatar-selection.react.js
@@ -6,6 +6,7 @@
 
 import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js';
 import { getDefaultAvatar } from 'lib/shared/avatar-utils.js';
+import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types';
 
 import RegistrationContainer from './registration-container.react.js';
 import RegistrationContentContainer from './registration-content-container.react.js';
@@ -38,7 +39,7 @@
 
   const { goBack } = props.navigation;
   const onSuccess = React.useCallback(
-    avatarRequest => {
+    (avatarRequest: UpdateUserAvatarRequest) => {
       goBack();
       return nativeSetUserAvatar(avatarRequest);
     },
diff --git a/native/account/siwe-hooks.js b/native/account/siwe-hooks.js
--- a/native/account/siwe-hooks.js
+++ b/native/account/siwe-hooks.js
@@ -4,7 +4,10 @@
 
 import { siweAuth, siweAuthActionTypes } from 'lib/actions/siwe-actions.js';
 import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js';
-import type { LogInStartingPayload } from 'lib/types/account-types.js';
+import type {
+  LogInStartingPayload,
+  LogInExtraInfo,
+} from 'lib/types/account-types.js';
 import {
   useServerCall,
   useDispatchActionPromise,
@@ -32,7 +35,12 @@
   const siweAuthCall = useServerCall(siweAuth);
 
   const callSIWE = React.useCallback(
-    async (message, signature, extraInfo, callServerEndpointOptions) => {
+    async (
+      message: string,
+      signature: string,
+      extraInfo: LogInExtraInfo,
+      callServerEndpointOptions: ?CallServerEndpointOptions,
+    ) => {
       try {
         return await siweAuthCall(
           {
diff --git a/native/account/siwe-panel.react.js b/native/account/siwe-panel.react.js
--- a/native/account/siwe-panel.react.js
+++ b/native/account/siwe-panel.react.js
@@ -32,6 +32,14 @@
 const siweAuthLoadingStatusSelector =
   createLoadingStatusSelector(siweAuthActionTypes);
 
+type WebViewMessageEvent = {
+  +nativeEvent: {
+    +data: string,
+    ...
+  },
+  ...
+};
+
 type Props = {
   +onClosed: () => mixed,
   +onClosing: () => mixed,
@@ -119,7 +127,7 @@
   const closeBottomSheet = bottomSheetRef.current?.close;
   const { closing, onSuccessfulWalletSignature } = props;
   const handleMessage = React.useCallback(
-    async event => {
+    async (event: WebViewMessageEvent) => {
       const data: SIWEWebViewMessage = JSON.parse(event.nativeEvent.data);
       if (data.type === 'siwe_success') {
         const { address, message, signature } = data;
diff --git a/native/avatars/emoji-avatar-creation.react.js b/native/avatars/emoji-avatar-creation.react.js
--- a/native/avatars/emoji-avatar-creation.react.js
+++ b/native/avatars/emoji-avatar-creation.react.js
@@ -11,12 +11,13 @@
 import type {
   UpdateUserAvatarRequest,
   ClientEmojiAvatar,
-} from 'lib/types/avatar-types';
+} from 'lib/types/avatar-types.js';
 
 import Avatar from './avatar.react.js';
 import Button from '../components/button.react.js';
 import ColorRows from '../components/color-rows.react.js';
 import EmojiKeyboard from '../components/emoji-keyboard.react.js';
+import type { EmojiSelection } from '../components/emoji-keyboard.react.js';
 import { useStyles } from '../themes/colors.js';
 
 type Props = {
@@ -60,7 +61,7 @@
     setPendingColor(resetEmojiAvatar.color);
   }, [savedEmojiAvatarFunc]);
 
-  const onEmojiSelected = React.useCallback(emoji => {
+  const onEmojiSelected = React.useCallback((emoji: EmojiSelection) => {
     setPendingEmoji(emoji.emoji);
   }, []);
 
diff --git a/native/calendar/calendar.react.js b/native/calendar/calendar.react.js
--- a/native/calendar/calendar.react.js
+++ b/native/calendar/calendar.react.js
@@ -215,7 +215,7 @@
   topLoaderWaitingToLeaveView = true;
   bottomLoaderWaitingToLeaveView = true;
   // We keep refs to the entries so CalendarInputBar can save them
-  entryRefs = new Map();
+  entryRefs: Map<string, ?InternalEntry> = new Map();
 
   constructor(props: Props) {
     super(props);
@@ -389,7 +389,12 @@
   static datesFromListData(
     lastLDWH: $ReadOnlyArray<CalendarItemWithHeight>,
     newLDWH: $ReadOnlyArray<CalendarItemWithHeight>,
-  ) {
+  ): {
+    +lastStartDate: Date,
+    +newStartDate: Date,
+    +lastEndDate: Date,
+    +newEndDate: Date,
+  } {
     const lastSecondItem = lastLDWH[1];
     const newSecondItem = newLDWH[1];
     invariant(
@@ -472,7 +477,7 @@
 
   // ESLint doesn't recognize that invariant always throws
   // eslint-disable-next-line consistent-return
-  renderItem = (row: { item: CalendarItemWithHeight, ... }) => {
+  renderItem = (row: { +item: CalendarItemWithHeight, ... }): React.Node => {
     const item = row.item;
     if (item.itemType === 'loader') {
       return <ListLoadingIndicator />;
@@ -500,7 +505,7 @@
     invariant(false, 'renderItem conditions should be exhaustive');
   };
 
-  renderSectionHeader = (item: SectionHeaderItem) => {
+  renderSectionHeader = (item: SectionHeaderItem): React.Node => {
     let date = prettyDate(item.dateString);
     if (dateString(new Date()) === item.dateString) {
       date += ' (today)';
@@ -519,7 +524,7 @@
     );
   };
 
-  renderSectionFooter = (item: SectionFooterItem) => {
+  renderSectionFooter = (item: SectionFooterItem): React.Node => {
     return (
       <SectionFooter
         dateString={item.dateString}
@@ -536,9 +541,11 @@
     });
   };
 
-  // ESLint doesn't recognize that invariant always throws
-  // eslint-disable-next-line consistent-return
-  static keyExtractor = (item: CalendarItemWithHeight | CalendarItem) => {
+  static keyExtractor = (
+    item: CalendarItemWithHeight | CalendarItem,
+    // ESLint doesn't recognize that invariant always throws
+    // eslint-disable-next-line consistent-return
+  ): string => {
     if (item.itemType === 'loader') {
       return item.key;
     } else if (item.itemType === 'header') {
@@ -554,7 +561,7 @@
   static getItemLayout = (
     data: ?$ReadOnlyArray<CalendarItemWithHeight>,
     index: number,
-  ) => {
+  ): { length: number, offset: number, index: number } => {
     if (!data) {
       return { length: 0, offset: 0, index };
     }
@@ -566,7 +573,7 @@
 
   // ESLint doesn't recognize that invariant always throws
   // eslint-disable-next-line consistent-return
-  static itemHeight = (item: CalendarItemWithHeight) => {
+  static itemHeight = (item: CalendarItemWithHeight): number => {
     if (item.itemType === 'loader') {
       return 56;
     } else if (item.itemType === 'header') {
@@ -580,11 +587,13 @@
     invariant(false, 'itemHeight conditions should be exhaustive');
   };
 
-  static heightOfItems = (data: $ReadOnlyArray<CalendarItemWithHeight>) => {
+  static heightOfItems = (
+    data: $ReadOnlyArray<CalendarItemWithHeight>,
+  ): number => {
     return _sum(data.map(Calendar.itemHeight));
   };
 
-  render() {
+  render(): React.Node {
     const { listDataWithHeights } = this.state;
     let flatList = null;
     if (listDataWithHeights) {
@@ -648,12 +657,12 @@
     );
   }
 
-  flatListHeight() {
+  flatListHeight(): number {
     const { safeAreaHeight, tabBarHeight } = this.props.dimensions;
     return safeAreaHeight - tabBarHeight;
   }
 
-  initialScrollIndex(data: $ReadOnlyArray<CalendarItemWithHeight>) {
+  initialScrollIndex(data: $ReadOnlyArray<CalendarItemWithHeight>): number {
     const todayIndex = _findIndex(['dateString', dateString(new Date())])(data);
     const heightOfTodayHeader = Calendar.itemHeight(data[todayIndex]);
 
@@ -811,14 +820,14 @@
     this.flatList.scrollToOffset({ offset, animated: true });
   }
 
-  heightMeasurerKey = (item: CalendarItem) => {
+  heightMeasurerKey = (item: CalendarItem): ?string => {
     if (item.itemType !== 'entryInfo') {
       return null;
     }
     return item.entryInfo.text;
   };
 
-  heightMeasurerDummy = (item: CalendarItem) => {
+  heightMeasurerDummy = (item: CalendarItem): React.MixedElement => {
     invariant(
       item.itemType === 'entryInfo',
       'NodeHeightMeasurer asked for dummy for non-entryInfo item',
@@ -826,7 +835,10 @@
     return dummyNodeForEntryHeightMeasurement(item.entryInfo.text);
   };
 
-  heightMeasurerMergeItem = (item: CalendarItem, height: ?number) => {
+  heightMeasurerMergeItem = (
+    item: CalendarItem,
+    height: ?number,
+  ): CalendarItemWithHeight => {
     if (item.itemType !== 'entryInfo') {
       return item;
     }
@@ -994,7 +1006,7 @@
     );
   }
 
-  loadMoreAbove = _throttle(() => {
+  loadMoreAbove: () => void = _throttle(() => {
     if (
       this.topLoadingFromScroll &&
       this.topLoaderWaitingToLeaveView &&
@@ -1004,7 +1016,7 @@
     }
   }, 1000);
 
-  loadMoreBelow = _throttle(() => {
+  loadMoreBelow: () => void = _throttle(() => {
     if (
       this.bottomLoadingFromScroll &&
       this.bottomLoaderWaitingToLeaveView &&
diff --git a/native/calendar/section-footer.react.js b/native/calendar/section-footer.react.js
--- a/native/calendar/section-footer.react.js
+++ b/native/calendar/section-footer.react.js
@@ -48,7 +48,7 @@
   +styles: typeof unboundStyles,
 };
 class SectionFooter extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     return (
       <TouchableWithoutFeedback onPress={this.props.onPressWhitespace}>
         <View style={this.props.styles.sectionFooter}>
diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js
--- a/native/chat/chat-input-bar.react.js
+++ b/native/chat/chat-input-bar.react.js
@@ -1,6 +1,7 @@
 // @flow
 
 import Icon from '@expo/vector-icons/Ionicons.js';
+import type { GenericNavigationAction } from '@react-navigation/core';
 import invariant from 'invariant';
 import _throttle from 'lodash/throttle.js';
 import * as React from 'react';
@@ -455,17 +456,17 @@
     this.sendButtonContainerStyle = { width: sendButtonContainerWidth };
   }
 
-  static mediaGalleryOpen(props: Props) {
+  static mediaGalleryOpen(props: Props): boolean {
     const { keyboardState } = props;
     return !!(keyboardState && keyboardState.mediaGalleryOpen);
   }
 
-  static systemKeyboardShowing(props: Props) {
+  static systemKeyboardShowing(props: Props): boolean {
     const { keyboardState } = props;
     return !!(keyboardState && keyboardState.systemKeyboardShowing);
   }
 
-  get systemKeyboardShowing() {
+  get systemKeyboardShowing(): boolean {
     return ChatInputBar.systemKeyboardShowing(this.props);
   }
 
@@ -629,7 +630,7 @@
     return checkIfDefaultMembersAreVoiced(this.props.threadInfo);
   }
 
-  render() {
+  render(): React.Node {
     const isMember = viewerIsMember(this.props.threadInfo);
     const canJoin = threadHasPermission(
       this.props.threadInfo,
@@ -805,7 +806,7 @@
     );
   }
 
-  renderInput() {
+  renderInput(): React.Node {
     const expandoButton = (
       <TouchableOpacity
         onPress={this.expandButtons}
@@ -929,7 +930,7 @@
     this.setState({ selectionState: data });
   };
 
-  saveDraft = _throttle(text => {
+  saveDraft: (text: string) => void = _throttle(text => {
     this.props.dispatch({
       type: updateDraftActionType,
       payload: {
@@ -1021,14 +1022,14 @@
     );
   };
 
-  isEditMode = () => {
+  isEditMode = (): boolean => {
     const editState = this.props.messageEditingContext?.editState;
     const isThisThread =
       editState?.editedMessage?.threadID === this.props.threadInfo.id;
-    return editState && editState.editedMessage !== null && isThisThread;
+    return editState?.editedMessage !== null && isThisThread;
   };
 
-  isMessageEdited = newText => {
+  isMessageEdited = (newText?: string): boolean => {
     let text = newText ?? this.state.text;
     text = trimMessage(text);
     const originalText = this.props.editedMessageInfo?.text;
@@ -1097,7 +1098,7 @@
     }
   };
 
-  getEditedMessage = () => {
+  getEditedMessage = (): ?MessageInfo => {
     const editState = this.props.messageEditingContext?.editState;
     return editState?.editedMessage;
   };
@@ -1144,7 +1145,11 @@
     );
   };
 
-  onNavigationBeforeRemove = e => {
+  onNavigationBeforeRemove = (e: {
+    +data: { +action: GenericNavigationAction },
+    +preventDefault: () => void,
+    ...
+  }) => {
     if (!this.isEditMode()) {
       return;
     }
@@ -1173,7 +1178,7 @@
     this.props.dispatchActionPromise(joinThreadActionTypes, this.joinAction());
   };
 
-  async joinAction() {
+  async joinAction(): Promise<ThreadJoinPayload> {
     const query = this.props.calendarQuery();
     return await this.props.joinThread({
       threadID: this.props.threadInfo.id,
diff --git a/native/chat/chat-list.react.js b/native/chat/chat-list.react.js
--- a/native/chat/chat-list.react.js
+++ b/native/chat/chat-list.react.js
@@ -70,7 +70,7 @@
   flatList: ?FlatListElementRef;
   scrollPos = 0;
 
-  newMessagesPillProgress = new Animated.Value(0);
+  newMessagesPillProgress: Animated.Value = new Animated.Value(0);
   newMessagesPillStyle: ViewStyle;
 
   constructor(props: Props) {
@@ -125,7 +125,7 @@
     }
   };
 
-  get scrolledToBottom() {
+  get scrolledToBottom(): boolean {
     return this.scrollPos <= 0;
   }
 
@@ -218,7 +218,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     const { navigation, viewerID, ...rest } = this.props;
     const { newMessageCount } = this.state;
     return (
@@ -250,7 +250,7 @@
   static getItemLayout = (
     data: ?$ReadOnlyArray<ChatMessageItemWithHeight>,
     index: number,
-  ) => {
+  ): { length: number, offset: number, index: number } => {
     if (!data) {
       return { length: 0, offset: 0, index };
     }
diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js
--- a/native/chat/chat.react.js
+++ b/native/chat/chat.react.js
@@ -13,6 +13,8 @@
   ParamListBase,
   StackRouterOptions,
   MaterialTopTabNavigationHelpers,
+  HeaderTitleInputProps,
+  StackHeaderLeftButtonProps,
 } from '@react-navigation/core';
 import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
 import {
@@ -72,6 +74,7 @@
   type ChatTopTabsParamList,
   MessageSearchRouteName,
   ChangeRolesScreenRouteName,
+  type NavigationRoute,
 } from '../navigation/route-names.js';
 import type { TabNavigationProp } from '../navigation/tab-navigator.react.js';
 import ChangeRolesHeaderLeftButton from '../roles/change-roles-header-left-button.react.js';
@@ -107,14 +110,14 @@
 const homeChatThreadListOptions = {
   title: 'Focused',
   // eslint-disable-next-line react/display-name
-  tabBarIcon: ({ color }) => (
+  tabBarIcon: ({ color }: { +color: string, ... }) => (
     <SWMansionIcon name="home-1" size={22} style={{ color }} />
   ),
 };
 const backgroundChatThreadListOptions = {
   title: 'Background',
   // eslint-disable-next-line react/display-name
-  tabBarIcon: ({ color }) => (
+  tabBarIcon: ({ color }: { +color: string, ... }) => (
     <SWMansionIcon name="bell-disabled" size={22} style={{ color }} />
   ),
 };
@@ -234,7 +237,13 @@
 
 const headerRightStyle = { flexDirection: 'row' };
 
-const messageListOptions = ({ navigation, route }) => {
+const messageListOptions = ({
+  navigation,
+  route,
+}: {
+  +navigation: ChatNavigationProp<'MessageList'>,
+  +route: NavigationRoute<'MessageList'>,
+}) => {
   const isSearchEmpty =
     !!route.params.searching && route.params.threadInfo.members.length === 1;
 
@@ -244,7 +253,7 @@
   return {
     // This is a render prop, not a component
     // eslint-disable-next-line react/display-name
-    headerTitle: props => (
+    headerTitle: (props: HeaderTitleInputProps) => (
       <MessageListHeaderTitle
         threadInfo={route.params.threadInfo}
         navigate={navigation.navigate}
@@ -279,9 +288,14 @@
   headerTitle: 'Compose chat',
   headerBackTitleVisible: false,
 };
-const threadSettingsOptions = ({ route }) => ({
+const threadSettingsOptions = ({
+  route,
+}: {
+  +route: NavigationRoute<'ThreadSettings'>,
+  ...
+}) => ({
   // eslint-disable-next-line react/display-name
-  headerTitle: props => (
+  headerTitle: (props: HeaderTitleInputProps) => (
     <ThreadSettingsHeaderTitle
       threadInfo={route.params.threadInfo}
       {...props}
@@ -313,9 +327,14 @@
   headerTitle: 'Pinned Messages',
   headerBackTitleVisible: false,
 };
-const changeRolesScreenOptions = ({ route }) => ({
+const changeRolesScreenOptions = ({
+  route,
+}: {
+  +route: NavigationRoute<'ChangeRolesScreen'>,
+  ...
+}) => ({
   // eslint-disable-next-line react/display-name
-  headerLeft: headerLeftProps => (
+  headerLeft: (headerLeftProps: StackHeaderLeftButtonProps) => (
     <ChangeRolesHeaderLeftButton {...headerLeftProps} route={route} />
   ),
   headerTitle: 'Change Role',
@@ -349,7 +368,7 @@
   }
 
   const headerLeftButton = React.useCallback(
-    headerProps => {
+    (headerProps: StackHeaderLeftButtonProps) => {
       if (headerProps.canGoBack) {
         return <HeaderBackButton {...headerProps} />;
       }
@@ -379,7 +398,12 @@
   );
 
   const chatThreadListOptions = React.useCallback(
-    ({ navigation }) => ({
+    ({
+      navigation,
+    }: {
+      +navigation: ChatNavigationProp<'ChatThreadList'>,
+      ...
+    }) => ({
       headerTitle: 'Inbox',
       headerRight:
         Platform.OS === 'ios'
diff --git a/native/chat/failed-send.react.js b/native/chat/failed-send.react.js
--- a/native/chat/failed-send.react.js
+++ b/native/chat/failed-send.react.js
@@ -86,7 +86,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     if (!this.props.rawMessageInfo) {
       return null;
     }
diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js
--- a/native/chat/message-list-container.react.js
+++ b/native/chat/message-list-container.react.js
@@ -107,7 +107,7 @@
   };
   pendingListDataWithHeights: ?$ReadOnlyArray<ChatMessageItemWithHeight>;
 
-  get frozen() {
+  get frozen(): boolean {
     const { overlayContext } = this.props;
     invariant(
       overlayContext,
@@ -155,7 +155,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     const { threadInfo, styles } = this.props;
     const { listDataWithHeights } = this.state;
     const { searching } = this.props.route.params;
diff --git a/native/chat/message-list-header-title.react.js b/native/chat/message-list-header-title.react.js
--- a/native/chat/message-list-header-title.react.js
+++ b/native/chat/message-list-header-title.react.js
@@ -43,7 +43,7 @@
   +title: string,
 };
 class MessageListHeaderTitle extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const {
       threadInfo,
       navigate,
diff --git a/native/chat/message-list.react.js b/native/chat/message-list.react.js
--- a/native/chat/message-list.react.js
+++ b/native/chat/message-list.react.js
@@ -113,40 +113,41 @@
   };
   flatListContainer: ?React.ElementRef<typeof View>;
 
-  flatListExtraDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.messageListVerticalBounds,
-    (propsAndState: PropsAndState) => propsAndState.focusedMessageKey,
-    (propsAndState: PropsAndState) => propsAndState.navigation,
-    (propsAndState: PropsAndState) => propsAndState.route,
-    (
-      messageListVerticalBounds: ?VerticalBounds,
-      focusedMessageKey: ?string,
-      navigation: ChatNavigationProp<'MessageList'>,
-      route: NavigationRoute<'MessageList'>,
-    ) => ({
-      messageListVerticalBounds,
-      focusedMessageKey,
-      navigation,
-      route,
-    }),
-  );
+  flatListExtraDataSelector: PropsAndState => FlatListExtraData =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.messageListVerticalBounds,
+      (propsAndState: PropsAndState) => propsAndState.focusedMessageKey,
+      (propsAndState: PropsAndState) => propsAndState.navigation,
+      (propsAndState: PropsAndState) => propsAndState.route,
+      (
+        messageListVerticalBounds: ?VerticalBounds,
+        focusedMessageKey: ?string,
+        navigation: ChatNavigationProp<'MessageList'>,
+        route: NavigationRoute<'MessageList'>,
+      ) => ({
+        messageListVerticalBounds,
+        focusedMessageKey,
+        navigation,
+        route,
+      }),
+    );
 
   get flatListExtraData(): FlatListExtraData {
     return this.flatListExtraDataSelector({ ...this.props, ...this.state });
   }
 
-  static getOverlayContext(props: Props) {
+  static getOverlayContext(props: Props): OverlayContextType {
     const { overlayContext } = props;
     invariant(overlayContext, 'MessageList should have OverlayContext');
     return overlayContext;
   }
 
-  static scrollDisabled(props: Props) {
+  static scrollDisabled(props: Props): boolean {
     const overlayContext = MessageList.getOverlayContext(props);
     return overlayContext.scrollBlockingModalStatus !== 'closed';
   }
 
-  static modalOpen(props: Props) {
+  static modalOpen(props: Props): boolean {
     const overlayContext = MessageList.getOverlayContext(props);
     return overlayContext.scrollBlockingModalStatus === 'open';
   }
@@ -174,7 +175,7 @@
     keyboardState && keyboardState.dismissKeyboard();
   };
 
-  renderItem = (row: { item: ChatMessageItemWithHeight, ... }) => {
+  renderItem = (row: { item: ChatMessageItemWithHeight, ... }): React.Node => {
     if (row.item.itemType === 'loader') {
       return (
         <TouchableWithoutFeedback onPress={this.dismissKeyboard}>
@@ -211,9 +212,11 @@
   };
 
   // Actually header, it's just that our FlatList is inverted
-  ListFooterComponent = () => <View style={this.props.styles.header} />;
+  ListFooterComponent = (): React.Node => (
+    <View style={this.props.styles.header} />
+  );
 
-  render() {
+  render(): React.Node {
     const { messageListData, startReached } = this.props;
     const footer = startReached ? this.ListFooterComponent : undefined;
     let relationshipPrompt = null;
diff --git a/native/chat/message-reactions-modal.react.js b/native/chat/message-reactions-modal.react.js
--- a/native/chat/message-reactions-modal.react.js
+++ b/native/chat/message-reactions-modal.react.js
@@ -12,7 +12,10 @@
 import { SafeAreaView } from 'react-native-safe-area-context';
 
 import type { ReactionInfo } from 'lib/selectors/chat-selectors.js';
-import { useMessageReactionsList } from 'lib/shared/reaction-utils.js';
+import {
+  useMessageReactionsList,
+  type MessageReactionListInfo,
+} from 'lib/shared/reaction-utils.js';
 
 import UserAvatar from '../avatars/user-avatar.react.js';
 import Modal from '../components/modal.react.js';
@@ -70,7 +73,7 @@
   );
 
   const renderItem = React.useCallback(
-    ({ item }) => (
+    ({ item }: { +item: MessageReactionListInfo, ... }) => (
       <TouchableOpacity
         onPress={() => onPressUser(item.id)}
         key={item.id}
diff --git a/native/chat/multimedia-message-multimedia.react.js b/native/chat/multimedia-message-multimedia.react.js
--- a/native/chat/multimedia-message-multimedia.react.js
+++ b/native/chat/multimedia-message-multimedia.react.js
@@ -74,7 +74,7 @@
     };
   }
 
-  static getOverlayContext(props: Props) {
+  static getOverlayContext(props: Props): OverlayContextType {
     const { overlayContext } = props;
     invariant(
       overlayContext,
@@ -83,7 +83,7 @@
     return overlayContext;
   }
 
-  static getModalOverlayPosition(props: Props) {
+  static getModalOverlayPosition(props: Props): ?Animated.Value {
     const overlayContext = MultimediaMessageMultimedia.getOverlayContext(props);
     const { visibleOverlays } = overlayContext;
     for (const overlay of visibleOverlays) {
@@ -98,7 +98,7 @@
     return undefined;
   }
 
-  getOpacity() {
+  getOpacity(): number | Animated.Node {
     const overlayPosition = MultimediaMessageMultimedia.getModalOverlayPosition(
       this.props,
     );
@@ -136,7 +136,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     const { opacity } = this.state;
     const animatedWrapperStyle: AnimatedStyleObj = { opacity };
     const wrapperStyles = [
@@ -202,7 +202,7 @@
     });
   };
 
-  dismissKeyboardIfShowing = () => {
+  dismissKeyboardIfShowing = (): boolean => {
     const { keyboardState } = this.props;
     return !!(keyboardState && keyboardState.dismissKeyboardIfShowing());
   };
diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js
--- a/native/chat/multimedia-message-tooltip-button.react.js
+++ b/native/chat/multimedia-message-tooltip-button.react.js
@@ -18,6 +18,7 @@
 import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js';
 import { useAnimatedMessageTooltipButton } from './utils.js';
 import EmojiKeyboard from '../components/emoji-keyboard.react.js';
+import type { EmojiSelection } from '../components/emoji-keyboard.react.js';
 import type { AppNavigationProp } from '../navigation/app-navigator.react.js';
 import { useSelector } from '../redux/redux-utils.js';
 import { useTooltipActions } from '../tooltip/tooltip-hooks.js';
@@ -147,7 +148,7 @@
   const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
 
   const onEmojiSelected = React.useCallback(
-    emoji => {
+    (emoji: EmojiSelection) => {
       sendReaction(emoji.emoji);
       dismissTooltip();
     },
diff --git a/native/chat/multimedia-message-tooltip-modal.react.js b/native/chat/multimedia-message-tooltip-modal.react.js
--- a/native/chat/multimedia-message-tooltip-modal.react.js
+++ b/native/chat/multimedia-message-tooltip-modal.react.js
@@ -16,6 +16,7 @@
 } from '../tooltip/tooltip.react.js';
 import type { ChatMultimediaMessageInfoItem } from '../types/chat-types.js';
 import type { VerticalBounds } from '../types/layout-types.js';
+import type { TextStyle } from '../types/styles.js';
 import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js';
 
 export type MultimediaMessageTooltipModalParams = TooltipParams<{
@@ -33,17 +34,21 @@
   const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
 
   const renderPinIcon = React.useCallback(
-    style => <CommIcon name="pin-outline" style={style} size={16} />,
+    (style: TextStyle) => (
+      <CommIcon name="pin-outline" style={style} size={16} />
+    ),
     [],
   );
   const renderUnpinIcon = React.useCallback(
-    style => <CommIcon name="unpin-outline" style={style} size={16} />,
+    (style: TextStyle) => (
+      <CommIcon name="unpin-outline" style={style} size={16} />
+    ),
     [],
   );
 
   const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
   const renderSidebarIcon = React.useCallback(
-    style => (
+    (style: TextStyle) => (
       <SWMansionIcon name="message-circle-lines" style={style} size={16} />
     ),
     [],
@@ -51,7 +56,9 @@
 
   const onPressReport = useOnPressReport(route);
   const renderReportIcon = React.useCallback(
-    style => <SWMansionIcon name="warning-circle" style={style} size={16} />,
+    (style: TextStyle) => (
+      <SWMansionIcon name="warning-circle" style={style} size={16} />
+    ),
     [],
   );
 
diff --git a/native/chat/multimedia-message.react.js b/native/chat/multimedia-message.react.js
--- a/native/chat/multimedia-message.react.js
+++ b/native/chat/multimedia-message.react.js
@@ -86,7 +86,7 @@
     });
   };
 
-  visibleEntryIDs() {
+  visibleEntryIDs(): $ReadOnlyArray<string> {
     const result = [];
 
     if (this.props.canTogglePins) {
@@ -182,14 +182,14 @@
     });
   };
 
-  canNavigateToSidebar() {
+  canNavigateToSidebar(): boolean {
     return (
-      this.props.item.threadCreatedFromMessage ||
+      !!this.props.item.threadCreatedFromMessage ||
       this.props.canCreateSidebarFromMessage
     );
   }
 
-  render() {
+  render(): React.Node {
     const {
       item,
       focused,
diff --git a/native/chat/robotext-message-tooltip-button.react.js b/native/chat/robotext-message-tooltip-button.react.js
--- a/native/chat/robotext-message-tooltip-button.react.js
+++ b/native/chat/robotext-message-tooltip-button.react.js
@@ -17,6 +17,7 @@
 import { Timestamp } from './timestamp.react.js';
 import { useAnimatedMessageTooltipButton } from './utils.js';
 import EmojiKeyboard from '../components/emoji-keyboard.react.js';
+import type { EmojiSelection } from '../components/emoji-keyboard.react.js';
 import type { AppNavigationProp } from '../navigation/app-navigator.react.js';
 import { useSelector } from '../redux/redux-utils.js';
 import { useTooltipActions } from '../tooltip/tooltip-hooks.js';
@@ -130,7 +131,7 @@
   const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
 
   const onEmojiSelected = React.useCallback(
-    emoji => {
+    (emoji: EmojiSelection) => {
       sendReaction(emoji.emoji);
       dismissTooltip();
     },
diff --git a/native/chat/robotext-message-tooltip-modal.react.js b/native/chat/robotext-message-tooltip-modal.react.js
--- a/native/chat/robotext-message-tooltip-modal.react.js
+++ b/native/chat/robotext-message-tooltip-modal.react.js
@@ -12,6 +12,7 @@
   type TooltipMenuProps,
 } from '../tooltip/tooltip.react.js';
 import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js';
+import type { TextStyle } from '../types/styles.js';
 
 export type RobotextMessageTooltipModalParams = TooltipParams<{
   +item: ChatRobotextMessageInfoItemWithHeight,
@@ -24,7 +25,7 @@
 
   const onPress = useAnimatedNavigateToSidebar(route.params.item);
   const renderIcon = React.useCallback(
-    style => (
+    (style: TextStyle) => (
       <SWMansionIcon name="message-circle-lines" style={style} size={16} />
     ),
     [],
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -106,7 +106,14 @@
   }, [item.threadCreatedFromMessage, canCreateSidebarFromMessage]);
 
   const openRobotextTooltipModal = React.useCallback(
-    (x, y, width, height, pageX, pageY) => {
+    (
+      x: number,
+      y: number,
+      width: number,
+      height: number,
+      pageX: number,
+      pageY: number,
+    ) => {
       invariant(
         verticalBounds,
         'verticalBounds should be present in openRobotextTooltipModal',
diff --git a/native/chat/settings/compose-subchannel-modal.react.js b/native/chat/settings/compose-subchannel-modal.react.js
--- a/native/chat/settings/compose-subchannel-modal.react.js
+++ b/native/chat/settings/compose-subchannel-modal.react.js
@@ -69,7 +69,7 @@
   +styles: typeof unboundStyles,
 };
 class ComposeSubchannelModal extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     return (
       <Modal modalStyle={this.props.styles.modal}>
         <Text style={this.props.styles.visibility}>Chat type</Text>
diff --git a/native/chat/settings/delete-thread.react.js b/native/chat/settings/delete-thread.react.js
--- a/native/chat/settings/delete-thread.react.js
+++ b/native/chat/settings/delete-thread.react.js
@@ -142,7 +142,7 @@
     this.mounted = false;
   }
 
-  render() {
+  render(): React.Node {
     const buttonContent =
       this.props.loadingStatus === 'loading' ? (
         <ActivityIndicator size="small" color="white" />
@@ -207,7 +207,7 @@
     );
   };
 
-  async deleteThread() {
+  async deleteThread(): Promise<LeaveThreadPayload> {
     const { threadInfo, navDispatch } = this.props;
     navDispatch({
       type: clearThreadsActionType,
diff --git a/native/chat/settings/emoji-thread-avatar-creation.react.js b/native/chat/settings/emoji-thread-avatar-creation.react.js
--- a/native/chat/settings/emoji-thread-avatar-creation.react.js
+++ b/native/chat/settings/emoji-thread-avatar-creation.react.js
@@ -5,6 +5,7 @@
 
 import { EditThreadAvatarContext } from 'lib/components/base-edit-thread-avatar-provider.react.js';
 import { savedEmojiAvatarSelectorForThread } from 'lib/selectors/thread-selectors.js';
+import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js';
 import type {
   MinimallyEncodedRawThreadInfo,
   MinimallyEncodedThreadInfo,
@@ -47,7 +48,7 @@
   const nativeSetThreadAvatar = useNativeSetThreadAvatar();
 
   const setAvatar = React.useCallback(
-    async avatarRequest => {
+    async (avatarRequest: UpdateUserAvatarRequest) => {
       const result = await nativeSetThreadAvatar(threadID, avatarRequest);
       displayActionResultModal('Avatar updated!');
       return result;
diff --git a/native/chat/settings/thread-settings-color.react.js b/native/chat/settings/thread-settings-color.react.js
--- a/native/chat/settings/thread-settings-color.react.js
+++ b/native/chat/settings/thread-settings-color.react.js
@@ -54,7 +54,7 @@
   +styles: typeof unboundStyles,
 };
 class ThreadSettingsColor extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     let colorButton;
     if (this.props.loadingStatus !== 'loading') {
       colorButton = (
diff --git a/native/chat/settings/thread-settings-description.react.js b/native/chat/settings/thread-settings-description.react.js
--- a/native/chat/settings/thread-settings-description.react.js
+++ b/native/chat/settings/thread-settings-description.react.js
@@ -111,7 +111,7 @@
 class ThreadSettingsDescription extends React.PureComponent<Props> {
   textInput: ?React.ElementRef<typeof BaseTextInput>;
 
-  render() {
+  render(): React.Node {
     if (
       this.props.descriptionEditValue !== null &&
       this.props.descriptionEditValue !== undefined
@@ -197,7 +197,7 @@
     return null;
   }
 
-  renderButton() {
+  renderButton(): React.Node {
     if (this.props.loadingStatus === 'loading') {
       return (
         <ActivityIndicator
@@ -264,7 +264,9 @@
     });
   };
 
-  async editDescription(newDescription: string) {
+  async editDescription(
+    newDescription: string,
+  ): Promise<ChangeThreadSettingsPayload> {
     try {
       return await this.props.changeThreadSettings({
         threadID: this.props.threadInfo.id,
diff --git a/native/chat/settings/thread-settings-home-notifs.react.js b/native/chat/settings/thread-settings-home-notifs.react.js
--- a/native/chat/settings/thread-settings-home-notifs.react.js
+++ b/native/chat/settings/thread-settings-home-notifs.react.js
@@ -66,7 +66,7 @@
     };
   }
 
-  render() {
+  render(): React.Node {
     const componentLabel = 'Background';
     return (
       <View style={this.props.styles.row}>
diff --git a/native/chat/settings/thread-settings-leave-thread.react.js b/native/chat/settings/thread-settings-leave-thread.react.js
--- a/native/chat/settings/thread-settings-leave-thread.react.js
+++ b/native/chat/settings/thread-settings-leave-thread.react.js
@@ -67,7 +67,7 @@
   +navContext: ?NavContextType,
 };
 class ThreadSettingsLeaveThread extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const { panelIosHighlightUnderlay, panelForegroundSecondaryLabel } =
       this.props.colors;
     const loadingIndicator =
@@ -122,7 +122,7 @@
     );
   };
 
-  async leaveThread() {
+  async leaveThread(): Promise<LeaveThreadPayload> {
     const threadID = this.props.threadInfo.id;
     const { navContext } = this.props;
     invariant(navContext, 'navContext should exist in leaveThread');
diff --git a/native/chat/settings/thread-settings-media-gallery.react.js b/native/chat/settings/thread-settings-media-gallery.react.js
--- a/native/chat/settings/thread-settings-media-gallery.react.js
+++ b/native/chat/settings/thread-settings-media-gallery.react.js
@@ -101,7 +101,7 @@
   }, [activeTab, mediaInfos]);
 
   const renderItem = React.useCallback(
-    ({ item, index }) => (
+    ({ item, index }: { +item: Media, +index: number, ... }) => (
       <MediaGalleryItem
         item={item}
         index={index}
diff --git a/native/chat/settings/thread-settings-member.react.js b/native/chat/settings/thread-settings-member.react.js
--- a/native/chat/settings/thread-settings-member.react.js
+++ b/native/chat/settings/thread-settings-member.react.js
@@ -119,7 +119,7 @@
 class ThreadSettingsMember extends React.PureComponent<Props> {
   editButton: ?React.ElementRef<typeof View>;
 
-  render() {
+  render(): React.Node {
     const userText = stringForUser(this.props.memberInfo);
 
     let usernameInfo = null;
@@ -248,7 +248,7 @@
     });
   };
 
-  dismissKeyboardIfShowing = () => {
+  dismissKeyboardIfShowing = (): boolean => {
     const { keyboardState } = this.props;
     return !!(keyboardState && keyboardState.dismissKeyboardIfShowing());
   };
diff --git a/native/chat/settings/thread-settings-name.react.js b/native/chat/settings/thread-settings-name.react.js
--- a/native/chat/settings/thread-settings-name.react.js
+++ b/native/chat/settings/thread-settings-name.react.js
@@ -83,7 +83,7 @@
 class ThreadSettingsName extends React.PureComponent<Props> {
   textInput: ?React.ElementRef<typeof BaseTextInput>;
 
-  render() {
+  render(): React.Node {
     return (
       <View style={this.props.styles.row}>
         <Text style={this.props.styles.label}>Name</Text>
@@ -92,7 +92,7 @@
     );
   }
 
-  renderButton() {
+  renderButton(): React.Node {
     if (this.props.loadingStatus === 'loading') {
       return (
         <ActivityIndicator
@@ -114,7 +114,7 @@
     return <SaveSettingButton onPress={this.onSubmit} />;
   }
 
-  renderContent() {
+  renderContent(): React.Node {
     if (
       this.props.nameEditValue === null ||
       this.props.nameEditValue === undefined
@@ -152,7 +152,7 @@
     this.textInput = textInput;
   };
 
-  threadEditName() {
+  threadEditName(): string {
     return firstLine(
       this.props.threadInfo.name ? this.props.threadInfo.name : '',
     );
@@ -191,7 +191,7 @@
     });
   };
 
-  async editName(newName: string) {
+  async editName(newName: string): Promise<ChangeThreadSettingsPayload> {
     try {
       return await this.props.changeThreadSettings({
         threadID: this.props.threadInfo.id,
diff --git a/native/chat/settings/thread-settings-promote-sidebar.react.js b/native/chat/settings/thread-settings-promote-sidebar.react.js
--- a/native/chat/settings/thread-settings-promote-sidebar.react.js
+++ b/native/chat/settings/thread-settings-promote-sidebar.react.js
@@ -59,7 +59,7 @@
     );
   };
 
-  render() {
+  render(): React.Node {
     const { panelIosHighlightUnderlay, panelForegroundSecondaryLabel } =
       this.props.colors;
     const loadingIndicator =
diff --git a/native/chat/settings/thread-settings-push-notifs.react.js b/native/chat/settings/thread-settings-push-notifs.react.js
--- a/native/chat/settings/thread-settings-push-notifs.react.js
+++ b/native/chat/settings/thread-settings-push-notifs.react.js
@@ -79,7 +79,7 @@
     };
   }
 
-  render() {
+  render(): React.Node {
     const componentLabel = 'Push notifs';
     let notificationsSettingsLinkingIcon: React.Node = undefined;
     if (!this.props.hasPushPermissions) {
diff --git a/native/chat/settings/thread-settings.react.js b/native/chat/settings/thread-settings.react.js
--- a/native/chat/settings/thread-settings.react.js
+++ b/native/chat/settings/thread-settings.react.js
@@ -312,7 +312,7 @@
     };
   }
 
-  static scrollDisabled(props: Props) {
+  static scrollDisabled(props: Props): boolean {
     const { overlayContext } = props;
     invariant(overlayContext, 'ThreadSettings should have OverlayContext');
     return overlayContext.scrollBlockingModalStatus !== 'closed';
@@ -340,548 +340,557 @@
     }
   }
 
-  threadBasicsListDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.threadInfo,
-    (propsAndState: PropsAndState) => propsAndState.parentThreadInfo,
-    (propsAndState: PropsAndState) => propsAndState.nameEditValue,
-    (propsAndState: PropsAndState) => propsAndState.colorEditValue,
-    (propsAndState: PropsAndState) => propsAndState.descriptionEditValue,
-    (propsAndState: PropsAndState) => propsAndState.descriptionTextHeight,
-    (propsAndState: PropsAndState) => !propsAndState.somethingIsSaving,
-    (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
-    (propsAndState: PropsAndState) => propsAndState.route.key,
-    (
-      threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
-      parentThreadInfo:
-        | ?ResolvedThreadInfo
-        | ?MinimallyEncodedResolvedThreadInfo,
-      nameEditValue: ?string,
-      colorEditValue: string,
-      descriptionEditValue: ?string,
-      descriptionTextHeight: ?number,
-      canStartEditing: boolean,
-      navigate: ThreadSettingsNavigate,
-      routeKey: string,
-    ) => {
-      const canEditThreadAvatar = threadHasPermission(
-        threadInfo,
-        threadPermissions.EDIT_THREAD_AVATAR,
-      );
-      const canEditThreadName = threadHasPermission(
-        threadInfo,
-        threadPermissions.EDIT_THREAD_NAME,
-      );
-      const canEditThreadDescription = threadHasPermission(
-        threadInfo,
-        threadPermissions.EDIT_THREAD_DESCRIPTION,
-      );
-      const canEditThreadColor = threadHasPermission(
-        threadInfo,
-        threadPermissions.EDIT_THREAD_COLOR,
-      );
+  threadBasicsListDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.threadInfo,
+      (propsAndState: PropsAndState) => propsAndState.parentThreadInfo,
+      (propsAndState: PropsAndState) => propsAndState.nameEditValue,
+      (propsAndState: PropsAndState) => propsAndState.colorEditValue,
+      (propsAndState: PropsAndState) => propsAndState.descriptionEditValue,
+      (propsAndState: PropsAndState) => propsAndState.descriptionTextHeight,
+      (propsAndState: PropsAndState) => !propsAndState.somethingIsSaving,
+      (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
+      (propsAndState: PropsAndState) => propsAndState.route.key,
+      (
+        threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
+        parentThreadInfo:
+          | ?ResolvedThreadInfo
+          | ?MinimallyEncodedResolvedThreadInfo,
+        nameEditValue: ?string,
+        colorEditValue: string,
+        descriptionEditValue: ?string,
+        descriptionTextHeight: ?number,
+        canStartEditing: boolean,
+        navigate: ThreadSettingsNavigate,
+        routeKey: string,
+      ) => {
+        const canEditThreadAvatar = threadHasPermission(
+          threadInfo,
+          threadPermissions.EDIT_THREAD_AVATAR,
+        );
+        const canEditThreadName = threadHasPermission(
+          threadInfo,
+          threadPermissions.EDIT_THREAD_NAME,
+        );
+        const canEditThreadDescription = threadHasPermission(
+          threadInfo,
+          threadPermissions.EDIT_THREAD_DESCRIPTION,
+        );
+        const canEditThreadColor = threadHasPermission(
+          threadInfo,
+          threadPermissions.EDIT_THREAD_COLOR,
+        );
 
-      const canChangeAvatar = canEditThreadAvatar && canStartEditing;
-      const canChangeName = canEditThreadName && canStartEditing;
-      const canChangeDescription = canEditThreadDescription && canStartEditing;
-      const canChangeColor = canEditThreadColor && canStartEditing;
-
-      const listData: ChatSettingsItem[] = [];
-      listData.push({
-        itemType: 'header',
-        key: 'avatarHeader',
-        title: 'Channel Avatar',
-        categoryType: 'unpadded',
-      });
-      listData.push({
-        itemType: 'avatar',
-        key: 'avatar',
-        threadInfo,
-        canChangeSettings: canChangeAvatar,
-      });
-      listData.push({
-        itemType: 'footer',
-        key: 'avatarFooter',
-        categoryType: 'outline',
-      });
-
-      listData.push({
-        itemType: 'header',
-        key: 'basicsHeader',
-        title: 'Basics',
-        categoryType: 'full',
-      });
-      listData.push({
-        itemType: 'name',
-        key: 'name',
-        threadInfo,
-        nameEditValue,
-        canChangeSettings: canChangeName,
-      });
-      listData.push({
-        itemType: 'color',
-        key: 'color',
-        threadInfo,
-        colorEditValue,
-        canChangeSettings: canChangeColor,
-        navigate,
-        threadSettingsRouteKey: routeKey,
-      });
-      listData.push({
-        itemType: 'footer',
-        key: 'basicsFooter',
-        categoryType: 'full',
-      });
+        const canChangeAvatar = canEditThreadAvatar && canStartEditing;
+        const canChangeName = canEditThreadName && canStartEditing;
+        const canChangeDescription =
+          canEditThreadDescription && canStartEditing;
+        const canChangeColor = canEditThreadColor && canStartEditing;
 
-      if (
-        (descriptionEditValue !== null && descriptionEditValue !== undefined) ||
-        threadInfo.description ||
-        canEditThreadDescription
-      ) {
+        const listData: ChatSettingsItem[] = [];
+        listData.push({
+          itemType: 'header',
+          key: 'avatarHeader',
+          title: 'Channel Avatar',
+          categoryType: 'unpadded',
+        });
         listData.push({
-          itemType: 'description',
-          key: 'description',
+          itemType: 'avatar',
+          key: 'avatar',
           threadInfo,
-          descriptionEditValue,
-          descriptionTextHeight,
-          canChangeSettings: canChangeDescription,
+          canChangeSettings: canChangeAvatar,
+        });
+        listData.push({
+          itemType: 'footer',
+          key: 'avatarFooter',
+          categoryType: 'outline',
         });
-      }
 
-      const isMember = viewerIsMember(threadInfo);
-      if (isMember) {
         listData.push({
           itemType: 'header',
-          key: 'subscriptionHeader',
-          title: 'Subscription',
+          key: 'basicsHeader',
+          title: 'Basics',
           categoryType: 'full',
         });
         listData.push({
-          itemType: 'pushNotifs',
-          key: 'pushNotifs',
+          itemType: 'name',
+          key: 'name',
+          threadInfo,
+          nameEditValue,
+          canChangeSettings: canChangeName,
+        });
+        listData.push({
+          itemType: 'color',
+          key: 'color',
           threadInfo,
+          colorEditValue,
+          canChangeSettings: canChangeColor,
+          navigate,
+          threadSettingsRouteKey: routeKey,
+        });
+        listData.push({
+          itemType: 'footer',
+          key: 'basicsFooter',
+          categoryType: 'full',
         });
-        if (threadInfo.type !== threadTypes.SIDEBAR) {
+
+        if (
+          (descriptionEditValue !== null &&
+            descriptionEditValue !== undefined) ||
+          threadInfo.description ||
+          canEditThreadDescription
+        ) {
+          listData.push({
+            itemType: 'description',
+            key: 'description',
+            threadInfo,
+            descriptionEditValue,
+            descriptionTextHeight,
+            canChangeSettings: canChangeDescription,
+          });
+        }
+
+        const isMember = viewerIsMember(threadInfo);
+        if (isMember) {
+          listData.push({
+            itemType: 'header',
+            key: 'subscriptionHeader',
+            title: 'Subscription',
+            categoryType: 'full',
+          });
           listData.push({
-            itemType: 'homeNotifs',
-            key: 'homeNotifs',
+            itemType: 'pushNotifs',
+            key: 'pushNotifs',
             threadInfo,
           });
+          if (threadInfo.type !== threadTypes.SIDEBAR) {
+            listData.push({
+              itemType: 'homeNotifs',
+              key: 'homeNotifs',
+              threadInfo,
+            });
+          }
+          listData.push({
+            itemType: 'footer',
+            key: 'subscriptionFooter',
+            categoryType: 'full',
+          });
         }
+
+        listData.push({
+          itemType: 'header',
+          key: 'privacyHeader',
+          title: 'Privacy',
+          categoryType: 'full',
+        });
+        listData.push({
+          itemType: 'visibility',
+          key: 'visibility',
+          threadInfo,
+        });
+        listData.push({
+          itemType: 'parent',
+          key: 'parent',
+          threadInfo,
+          parentThreadInfo,
+        });
         listData.push({
           itemType: 'footer',
-          key: 'subscriptionFooter',
+          key: 'privacyFooter',
           categoryType: 'full',
         });
-      }
-
-      listData.push({
-        itemType: 'header',
-        key: 'privacyHeader',
-        title: 'Privacy',
-        categoryType: 'full',
-      });
-      listData.push({
-        itemType: 'visibility',
-        key: 'visibility',
-        threadInfo,
-      });
-      listData.push({
-        itemType: 'parent',
-        key: 'parent',
-        threadInfo,
-        parentThreadInfo,
-      });
-      listData.push({
-        itemType: 'footer',
-        key: 'privacyFooter',
-        categoryType: 'full',
-      });
-      return listData;
-    },
-  );
-
-  subchannelsListDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.threadInfo,
-    (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
-    (propsAndState: PropsAndState) => propsAndState.childThreadInfos,
-    (propsAndState: PropsAndState) => propsAndState.numSubchannelsShowing,
-    (
-      threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
-      navigate: ThreadSettingsNavigate,
-      childThreads: ?$ReadOnlyArray<
-        ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
-      >,
-      numSubchannelsShowing: number,
-    ) => {
-      const listData: ChatSettingsItem[] = [];
-
-      const subchannels = childThreads?.filter(threadIsChannel) ?? [];
-      const canCreateSubchannels = threadHasPermission(
-        threadInfo,
-        threadPermissions.CREATE_SUBCHANNELS,
-      );
-      if (subchannels.length === 0 && !canCreateSubchannels) {
         return listData;
-      }
+      },
+    );
 
-      listData.push({
-        itemType: 'header',
-        key: 'subchannelHeader',
-        title: 'Subchannels',
-        categoryType: 'unpadded',
-      });
+  subchannelsListDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.threadInfo,
+      (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
+      (propsAndState: PropsAndState) => propsAndState.childThreadInfos,
+      (propsAndState: PropsAndState) => propsAndState.numSubchannelsShowing,
+      (
+        threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
+        navigate: ThreadSettingsNavigate,
+        childThreads: ?$ReadOnlyArray<
+          ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
+        >,
+        numSubchannelsShowing: number,
+      ) => {
+        const listData: ChatSettingsItem[] = [];
+
+        const subchannels = childThreads?.filter(threadIsChannel) ?? [];
+        const canCreateSubchannels = threadHasPermission(
+          threadInfo,
+          threadPermissions.CREATE_SUBCHANNELS,
+        );
+        if (subchannels.length === 0 && !canCreateSubchannels) {
+          return listData;
+        }
 
-      if (canCreateSubchannels) {
         listData.push({
-          itemType: 'addSubchannel',
-          key: 'addSubchannel',
+          itemType: 'header',
+          key: 'subchannelHeader',
+          title: 'Subchannels',
+          categoryType: 'unpadded',
         });
-      }
 
-      const numItems = Math.min(numSubchannelsShowing, subchannels.length);
-      for (let i = 0; i < numItems; i++) {
-        const subchannelInfo = subchannels[i];
+        if (canCreateSubchannels) {
+          listData.push({
+            itemType: 'addSubchannel',
+            key: 'addSubchannel',
+          });
+        }
+
+        const numItems = Math.min(numSubchannelsShowing, subchannels.length);
+        for (let i = 0; i < numItems; i++) {
+          const subchannelInfo = subchannels[i];
+          listData.push({
+            itemType: 'childThread',
+            key: `childThread${subchannelInfo.id}`,
+            threadInfo: subchannelInfo,
+            firstListItem: i === 0 && !canCreateSubchannels,
+            lastListItem: i === numItems - 1 && numItems === subchannels.length,
+          });
+        }
+
+        if (numItems < subchannels.length) {
+          listData.push({
+            itemType: 'seeMore',
+            key: 'seeMoreSubchannels',
+            onPress: this.onPressSeeMoreSubchannels,
+          });
+        }
+
         listData.push({
-          itemType: 'childThread',
-          key: `childThread${subchannelInfo.id}`,
-          threadInfo: subchannelInfo,
-          firstListItem: i === 0 && !canCreateSubchannels,
-          lastListItem: i === numItems - 1 && numItems === subchannels.length,
+          itemType: 'footer',
+          key: 'subchannelFooter',
+          categoryType: 'unpadded',
         });
-      }
 
-      if (numItems < subchannels.length) {
+        return listData;
+      },
+    );
+
+  sidebarsListDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
+      (propsAndState: PropsAndState) => propsAndState.childThreadInfos,
+      (propsAndState: PropsAndState) => propsAndState.numSidebarsShowing,
+      (
+        navigate: ThreadSettingsNavigate,
+        childThreads: ?$ReadOnlyArray<
+          ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
+        >,
+        numSidebarsShowing: number,
+      ) => {
+        const listData: ChatSettingsItem[] = [];
+
+        const sidebars =
+          childThreads?.filter(
+            childThreadInfo => childThreadInfo.type === threadTypes.SIDEBAR,
+          ) ?? [];
+        if (sidebars.length === 0) {
+          return listData;
+        }
+
         listData.push({
-          itemType: 'seeMore',
-          key: 'seeMoreSubchannels',
-          onPress: this.onPressSeeMoreSubchannels,
+          itemType: 'header',
+          key: 'sidebarHeader',
+          title: 'Threads',
+          categoryType: 'unpadded',
+        });
+
+        const numItems = Math.min(numSidebarsShowing, sidebars.length);
+        for (let i = 0; i < numItems; i++) {
+          const sidebarInfo = sidebars[i];
+          listData.push({
+            itemType: 'childThread',
+            key: `childThread${sidebarInfo.id}`,
+            threadInfo: sidebarInfo,
+            firstListItem: i === 0,
+            lastListItem: i === numItems - 1 && numItems === sidebars.length,
+          });
+        }
+
+        if (numItems < sidebars.length) {
+          listData.push({
+            itemType: 'seeMore',
+            key: 'seeMoreSidebars',
+            onPress: this.onPressSeeMoreSidebars,
+          });
+        }
+
+        listData.push({
+          itemType: 'footer',
+          key: 'sidebarFooter',
+          categoryType: 'unpadded',
         });
-      }
 
-      listData.push({
-        itemType: 'footer',
-        key: 'subchannelFooter',
-        categoryType: 'unpadded',
-      });
-
-      return listData;
-    },
-  );
-
-  sidebarsListDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
-    (propsAndState: PropsAndState) => propsAndState.childThreadInfos,
-    (propsAndState: PropsAndState) => propsAndState.numSidebarsShowing,
-    (
-      navigate: ThreadSettingsNavigate,
-      childThreads: ?$ReadOnlyArray<
-        ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
-      >,
-      numSidebarsShowing: number,
-    ) => {
-      const listData: ChatSettingsItem[] = [];
-
-      const sidebars =
-        childThreads?.filter(
-          childThreadInfo => childThreadInfo.type === threadTypes.SIDEBAR,
-        ) ?? [];
-      if (sidebars.length === 0) {
         return listData;
-      }
+      },
+    );
 
-      listData.push({
-        itemType: 'header',
-        key: 'sidebarHeader',
-        title: 'Threads',
-        categoryType: 'unpadded',
-      });
+  threadMembersListDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.threadInfo,
+      (propsAndState: PropsAndState) => !propsAndState.somethingIsSaving,
+      (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
+      (propsAndState: PropsAndState) => propsAndState.route.key,
+      (propsAndState: PropsAndState) => propsAndState.numMembersShowing,
+      (propsAndState: PropsAndState) => propsAndState.verticalBounds,
+      (
+        threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
+        canStartEditing: boolean,
+        navigate: ThreadSettingsNavigate,
+        routeKey: string,
+        numMembersShowing: number,
+        verticalBounds: ?VerticalBounds,
+      ) => {
+        const listData: ChatSettingsItem[] = [];
+
+        const canAddMembers = threadHasPermission(
+          threadInfo,
+          threadPermissions.ADD_MEMBERS,
+        );
+        if (threadInfo.members.length === 0 && !canAddMembers) {
+          return listData;
+        }
 
-      const numItems = Math.min(numSidebarsShowing, sidebars.length);
-      for (let i = 0; i < numItems; i++) {
-        const sidebarInfo = sidebars[i];
         listData.push({
-          itemType: 'childThread',
-          key: `childThread${sidebarInfo.id}`,
-          threadInfo: sidebarInfo,
-          firstListItem: i === 0,
-          lastListItem: i === numItems - 1 && numItems === sidebars.length,
+          itemType: 'header',
+          key: 'memberHeader',
+          title: 'Members',
+          categoryType: 'unpadded',
         });
-      }
 
-      if (numItems < sidebars.length) {
+        if (canAddMembers) {
+          listData.push({
+            itemType: 'addMember',
+            key: 'addMember',
+          });
+        }
+
+        const numItems = Math.min(numMembersShowing, threadInfo.members.length);
+        for (let i = 0; i < numItems; i++) {
+          const memberInfo = threadInfo.members[i];
+          listData.push({
+            itemType: 'member',
+            key: `member${memberInfo.id}`,
+            memberInfo,
+            threadInfo,
+            canEdit: canStartEditing,
+            navigate,
+            firstListItem: i === 0 && !canAddMembers,
+            lastListItem:
+              i === numItems - 1 && numItems === threadInfo.members.length,
+            verticalBounds,
+            threadSettingsRouteKey: routeKey,
+          });
+        }
+
+        if (numItems < threadInfo.members.length) {
+          listData.push({
+            itemType: 'seeMore',
+            key: 'seeMoreMembers',
+            onPress: this.onPressSeeMoreMembers,
+          });
+        }
+
         listData.push({
-          itemType: 'seeMore',
-          key: 'seeMoreSidebars',
-          onPress: this.onPressSeeMoreSidebars,
+          itemType: 'footer',
+          key: 'memberFooter',
+          categoryType: 'unpadded',
         });
-      }
 
-      listData.push({
-        itemType: 'footer',
-        key: 'sidebarFooter',
-        categoryType: 'unpadded',
-      });
-
-      return listData;
-    },
-  );
-
-  threadMembersListDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.threadInfo,
-    (propsAndState: PropsAndState) => !propsAndState.somethingIsSaving,
-    (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
-    (propsAndState: PropsAndState) => propsAndState.route.key,
-    (propsAndState: PropsAndState) => propsAndState.numMembersShowing,
-    (propsAndState: PropsAndState) => propsAndState.verticalBounds,
-    (
-      threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
-      canStartEditing: boolean,
-      navigate: ThreadSettingsNavigate,
-      routeKey: string,
-      numMembersShowing: number,
-      verticalBounds: ?VerticalBounds,
-    ) => {
-      const listData: ChatSettingsItem[] = [];
-
-      const canAddMembers = threadHasPermission(
-        threadInfo,
-        threadPermissions.ADD_MEMBERS,
-      );
-      if (threadInfo.members.length === 0 && !canAddMembers) {
         return listData;
-      }
+      },
+    );
 
-      listData.push({
-        itemType: 'header',
-        key: 'memberHeader',
-        title: 'Members',
-        categoryType: 'unpadded',
-      });
+  mediaGalleryListDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.threadInfo,
+      (propsAndState: PropsAndState) => propsAndState.verticalBounds,
+      (
+        threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
+        verticalBounds: ?VerticalBounds,
+      ) => {
+        const listData: ChatSettingsItem[] = [];
+        const limit = 6;
 
-      if (canAddMembers) {
         listData.push({
-          itemType: 'addMember',
-          key: 'addMember',
+          itemType: 'actionHeader',
+          key: 'mediaGalleryHeader',
+          title: 'Media Gallery',
+          actionText: 'See more',
+          onPress: this.onPressSeeMoreMediaGallery,
         });
-      }
 
-      const numItems = Math.min(numMembersShowing, threadInfo.members.length);
-      for (let i = 0; i < numItems; i++) {
-        const memberInfo = threadInfo.members[i];
         listData.push({
-          itemType: 'member',
-          key: `member${memberInfo.id}`,
-          memberInfo,
+          itemType: 'mediaGallery',
+          key: 'mediaGallery',
           threadInfo,
-          canEdit: canStartEditing,
-          navigate,
-          firstListItem: i === 0 && !canAddMembers,
-          lastListItem:
-            i === numItems - 1 && numItems === threadInfo.members.length,
+          limit,
           verticalBounds,
-          threadSettingsRouteKey: routeKey,
         });
-      }
 
-      if (numItems < threadInfo.members.length) {
         listData.push({
-          itemType: 'seeMore',
-          key: 'seeMoreMembers',
-          onPress: this.onPressSeeMoreMembers,
+          itemType: 'footer',
+          key: 'mediaGalleryFooter',
+          categoryType: 'outline',
         });
-      }
 
-      listData.push({
-        itemType: 'footer',
-        key: 'memberFooter',
-        categoryType: 'unpadded',
-      });
-
-      return listData;
-    },
-  );
-
-  mediaGalleryListDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.threadInfo,
-    (propsAndState: PropsAndState) => propsAndState.verticalBounds,
-    (
-      threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
-      verticalBounds: ?VerticalBounds,
-    ) => {
-      const listData: ChatSettingsItem[] = [];
-      const limit = 6;
-
-      listData.push({
-        itemType: 'actionHeader',
-        key: 'mediaGalleryHeader',
-        title: 'Media Gallery',
-        actionText: 'See more',
-        onPress: this.onPressSeeMoreMediaGallery,
-      });
-
-      listData.push({
-        itemType: 'mediaGallery',
-        key: 'mediaGallery',
-        threadInfo,
-        limit,
-        verticalBounds,
-      });
-
-      listData.push({
-        itemType: 'footer',
-        key: 'mediaGalleryFooter',
-        categoryType: 'outline',
-      });
-
-      return listData;
-    },
-  );
-
-  actionsListDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.threadInfo,
-    (propsAndState: PropsAndState) => propsAndState.parentThreadInfo,
-    (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
-    (propsAndState: PropsAndState) => propsAndState.styles,
-    (propsAndState: PropsAndState) => propsAndState.userInfos,
-    (propsAndState: PropsAndState) => propsAndState.viewerID,
-    (
-      threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
-      parentThreadInfo:
-        | ?ResolvedThreadInfo
-        | ?MinimallyEncodedResolvedThreadInfo,
-      navigate: ThreadSettingsNavigate,
-      styles: typeof unboundStyles,
-      userInfos: UserInfos,
-      viewerID: ?string,
-    ) => {
-      const buttons = [];
-
-      if (this.props.canPromoteSidebar) {
-        buttons.push({
-          itemType: 'promoteSidebar',
-          key: 'promoteSidebar',
-          threadInfo,
-          navigate,
-        });
-      }
+        return listData;
+      },
+    );
 
-      const canLeaveThread = threadHasPermission(
-        threadInfo,
-        threadPermissions.LEAVE_THREAD,
-      );
+  actionsListDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.threadInfo,
+      (propsAndState: PropsAndState) => propsAndState.parentThreadInfo,
+      (propsAndState: PropsAndState) => propsAndState.navigation.navigate,
+      (propsAndState: PropsAndState) => propsAndState.styles,
+      (propsAndState: PropsAndState) => propsAndState.userInfos,
+      (propsAndState: PropsAndState) => propsAndState.viewerID,
+      (
+        threadInfo: ResolvedThreadInfo | MinimallyEncodedResolvedThreadInfo,
+        parentThreadInfo:
+          | ?ResolvedThreadInfo
+          | ?MinimallyEncodedResolvedThreadInfo,
+        navigate: ThreadSettingsNavigate,
+        styles: typeof unboundStyles,
+        userInfos: UserInfos,
+        viewerID: ?string,
+      ) => {
+        const buttons = [];
+
+        if (this.props.canPromoteSidebar) {
+          buttons.push({
+            itemType: 'promoteSidebar',
+            key: 'promoteSidebar',
+            threadInfo,
+            navigate,
+          });
+        }
 
-      if (viewerIsMember(threadInfo) && canLeaveThread) {
-        buttons.push({
-          itemType: 'leaveThread',
-          key: 'leaveThread',
+        const canLeaveThread = threadHasPermission(
           threadInfo,
-          navigate,
-        });
-      }
+          threadPermissions.LEAVE_THREAD,
+        );
 
-      const canDeleteThread = threadHasPermission(
-        threadInfo,
-        threadPermissions.DELETE_THREAD,
-      );
-      if (canDeleteThread) {
-        buttons.push({
-          itemType: 'deleteThread',
-          key: 'deleteThread',
+        if (viewerIsMember(threadInfo) && canLeaveThread) {
+          buttons.push({
+            itemType: 'leaveThread',
+            key: 'leaveThread',
+            threadInfo,
+            navigate,
+          });
+        }
+
+        const canDeleteThread = threadHasPermission(
           threadInfo,
-          navigate,
-        });
-      }
+          threadPermissions.DELETE_THREAD,
+        );
+        if (canDeleteThread) {
+          buttons.push({
+            itemType: 'deleteThread',
+            key: 'deleteThread',
+            threadInfo,
+            navigate,
+          });
+        }
 
-      const threadIsPersonal = threadInfo.type === threadTypes.PERSONAL;
-      if (threadIsPersonal && viewerID) {
-        const otherMemberID = getSingleOtherUser(threadInfo, viewerID);
-        if (otherMemberID) {
-          const otherUserInfo = userInfos[otherMemberID];
-          const availableRelationshipActions =
-            getAvailableRelationshipButtons(otherUserInfo);
-
-          for (const action of availableRelationshipActions) {
-            buttons.push({
-              itemType: 'editRelationship',
-              key: action,
-              threadInfo,
-              navigate,
-              relationshipButton: action,
+        const threadIsPersonal = threadInfo.type === threadTypes.PERSONAL;
+        if (threadIsPersonal && viewerID) {
+          const otherMemberID = getSingleOtherUser(threadInfo, viewerID);
+          if (otherMemberID) {
+            const otherUserInfo = userInfos[otherMemberID];
+            const availableRelationshipActions =
+              getAvailableRelationshipButtons(otherUserInfo);
+
+            for (const action of availableRelationshipActions) {
+              buttons.push({
+                itemType: 'editRelationship',
+                key: action,
+                threadInfo,
+                navigate,
+                relationshipButton: action,
+              });
+            }
+          }
+        }
+
+        const listData: ChatSettingsItem[] = [];
+        if (buttons.length === 0) {
+          return listData;
+        }
+
+        listData.push({
+          itemType: 'header',
+          key: 'actionsHeader',
+          title: 'Actions',
+          categoryType: 'unpadded',
+        });
+        for (let i = 0; i < buttons.length; i++) {
+          // Necessary for Flow...
+          if (buttons[i].itemType === 'editRelationship') {
+            listData.push({
+              ...buttons[i],
+              buttonStyle: [
+                i === 0 ? null : styles.nonTopButton,
+                i === buttons.length - 1 ? styles.lastButton : null,
+              ],
+            });
+          } else {
+            listData.push({
+              ...buttons[i],
+              buttonStyle: [
+                i === 0 ? null : styles.nonTopButton,
+                i === buttons.length - 1 ? styles.lastButton : null,
+              ],
             });
           }
         }
-      }
+        listData.push({
+          itemType: 'footer',
+          key: 'actionsFooter',
+          categoryType: 'unpadded',
+        });
 
-      const listData: ChatSettingsItem[] = [];
-      if (buttons.length === 0) {
         return listData;
-      }
+      },
+    );
 
-      listData.push({
-        itemType: 'header',
-        key: 'actionsHeader',
-        title: 'Actions',
-        categoryType: 'unpadded',
-      });
-      for (let i = 0; i < buttons.length; i++) {
-        // Necessary for Flow...
-        if (buttons[i].itemType === 'editRelationship') {
-          listData.push({
-            ...buttons[i],
-            buttonStyle: [
-              i === 0 ? null : styles.nonTopButton,
-              i === buttons.length - 1 ? styles.lastButton : null,
-            ],
-          });
-        } else {
-          listData.push({
-            ...buttons[i],
-            buttonStyle: [
-              i === 0 ? null : styles.nonTopButton,
-              i === buttons.length - 1 ? styles.lastButton : null,
-            ],
-          });
-        }
-      }
-      listData.push({
-        itemType: 'footer',
-        key: 'actionsFooter',
-        categoryType: 'unpadded',
-      });
-
-      return listData;
-    },
-  );
-
-  listDataSelector = createSelector(
-    this.threadBasicsListDataSelector,
-    this.subchannelsListDataSelector,
-    this.sidebarsListDataSelector,
-    this.threadMembersListDataSelector,
-    this.mediaGalleryListDataSelector,
-    this.actionsListDataSelector,
-    (
-      threadBasicsListData: ChatSettingsItem[],
-      subchannelsListData: ChatSettingsItem[],
-      sidebarsListData: ChatSettingsItem[],
-      threadMembersListData: ChatSettingsItem[],
-      mediaGalleryListData: ChatSettingsItem[],
-      actionsListData: ChatSettingsItem[],
-    ) => [
-      ...threadBasicsListData,
-      ...subchannelsListData,
-      ...sidebarsListData,
-      ...threadMembersListData,
-      ...mediaGalleryListData,
-      ...actionsListData,
-    ],
-  );
-
-  get listData() {
+  listDataSelector: PropsAndState => $ReadOnlyArray<ChatSettingsItem> =
+    createSelector(
+      this.threadBasicsListDataSelector,
+      this.subchannelsListDataSelector,
+      this.sidebarsListDataSelector,
+      this.threadMembersListDataSelector,
+      this.mediaGalleryListDataSelector,
+      this.actionsListDataSelector,
+      (
+        threadBasicsListData: $ReadOnlyArray<ChatSettingsItem>,
+        subchannelsListData: $ReadOnlyArray<ChatSettingsItem>,
+        sidebarsListData: $ReadOnlyArray<ChatSettingsItem>,
+        threadMembersListData: $ReadOnlyArray<ChatSettingsItem>,
+        mediaGalleryListData: $ReadOnlyArray<ChatSettingsItem>,
+        actionsListData: $ReadOnlyArray<ChatSettingsItem>,
+      ) => [
+        ...threadBasicsListData,
+        ...subchannelsListData,
+        ...sidebarsListData,
+        ...threadMembersListData,
+        ...mediaGalleryListData,
+        ...actionsListData,
+      ],
+    );
+
+  get listData(): $ReadOnlyArray<ChatSettingsItem> {
     return this.listDataSelector({ ...this.props, ...this.state });
   }
 
-  render() {
+  render(): React.Node {
     return (
       <View
         style={this.props.styles.container}
@@ -933,7 +942,7 @@
 
   // ESLint doesn't recognize that invariant always throws
   // eslint-disable-next-line consistent-return
-  renderItem = (row: { item: ChatSettingsItem, ... }) => {
+  renderItem = (row: { +item: ChatSettingsItem, ... }): React.Node => {
     const item = row.item;
     if (item.itemType === 'header') {
       return (
diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js
--- a/native/chat/text-message-tooltip-button.react.js
+++ b/native/chat/text-message-tooltip-button.react.js
@@ -20,6 +20,7 @@
 import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js';
 import { useAnimatedMessageTooltipButton } from './utils.js';
 import EmojiKeyboard from '../components/emoji-keyboard.react.js';
+import type { EmojiSelection } from '../components/emoji-keyboard.react.js';
 import type { AppNavigationProp } from '../navigation/app-navigator.react.js';
 import { useSelector } from '../redux/redux-utils.js';
 import { useTooltipActions } from '../tooltip/tooltip-hooks.js';
@@ -144,7 +145,7 @@
   const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
 
   const onEmojiSelected = React.useCallback(
-    emoji => {
+    (emoji: EmojiSelection) => {
       sendReaction(emoji.emoji);
       dismissTooltip();
     },
diff --git a/native/chat/text-message-tooltip-modal.react.js b/native/chat/text-message-tooltip-modal.react.js
--- a/native/chat/text-message-tooltip-modal.react.js
+++ b/native/chat/text-message-tooltip-modal.react.js
@@ -23,6 +23,7 @@
   type TooltipMenuProps,
 } from '../tooltip/tooltip.react.js';
 import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js';
+import type { TextStyle } from '../types/styles.js';
 import { exitEditAlert } from '../utils/edit-messages-utils.js';
 import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js';
 
@@ -55,13 +56,13 @@
     });
   }, [inputState, navigateToThread, threadInfo, text]);
   const renderReplyIcon = React.useCallback(
-    style => <CommIcon name="reply" style={style} size={12} />,
+    (style: TextStyle) => <CommIcon name="reply" style={style} size={12} />,
     [],
   );
 
   const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
   const renderSidebarIcon = React.useCallback(
-    style => (
+    (style: TextStyle) => (
       <SWMansionIcon name="message-circle-lines" style={style} size={16} />
     ),
     [],
@@ -96,18 +97,24 @@
     }
   }, [inputState, messageEditingContext, messageInfo, text]);
   const renderEditIcon = React.useCallback(
-    style => <SWMansionIcon name="edit-1" style={style} size={16} />,
+    (style: TextStyle) => (
+      <SWMansionIcon name="edit-1" style={style} size={16} />
+    ),
     [],
   );
 
   const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
 
   const renderPinIcon = React.useCallback(
-    style => <CommIcon name="pin-outline" style={style} size={16} />,
+    (style: TextStyle) => (
+      <CommIcon name="pin-outline" style={style} size={16} />
+    ),
     [],
   );
   const renderUnpinIcon = React.useCallback(
-    style => <CommIcon name="unpin-outline" style={style} size={16} />,
+    (style: TextStyle) => (
+      <CommIcon name="unpin-outline" style={style} size={16} />
+    ),
     [],
   );
 
@@ -116,13 +123,15 @@
     setTimeout(confirmCopy);
   }, [text]);
   const renderCopyIcon = React.useCallback(
-    style => <SWMansionIcon name="copy" style={style} size={16} />,
+    (style: TextStyle) => <SWMansionIcon name="copy" style={style} size={16} />,
     [],
   );
 
   const onPressReport = useOnPressReport(route);
   const renderReportIcon = React.useCallback(
-    style => <SWMansionIcon name="warning-circle" style={style} size={16} />,
+    (style: TextStyle) => (
+      <SWMansionIcon name="warning-circle" style={style} size={16} />
+    ),
     [],
   );
 
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -78,7 +78,7 @@
     };
   }
 
-  render() {
+  render(): React.Node {
     const {
       item,
       navigation,
@@ -136,21 +136,21 @@
     this.message = message;
   };
 
-  canReply() {
+  canReply(): boolean {
     return threadHasPermission(
       this.props.item.threadInfo,
       threadPermissions.VOICED,
     );
   }
 
-  canNavigateToSidebar() {
+  canNavigateToSidebar(): boolean {
     return (
-      this.props.item.threadCreatedFromMessage ||
+      !!this.props.item.threadCreatedFromMessage ||
       this.props.canCreateSidebarFromMessage
     );
   }
 
-  visibleEntryIDs() {
+  visibleEntryIDs(): $ReadOnlyArray<string> {
     const result = ['copy'];
 
     if (this.canReply()) {
diff --git a/native/chat/thread-settings-button.react.js b/native/chat/thread-settings-button.react.js
--- a/native/chat/thread-settings-button.react.js
+++ b/native/chat/thread-settings-button.react.js
@@ -28,7 +28,7 @@
 };
 
 class ThreadSettingsButton extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     return (
       <Button onPress={this.onPress} androidBorderlessRipple={true}>
         <SWMansionIcon
diff --git a/native/community-creation/community-configuration.react.js b/native/community-creation/community-configuration.react.js
--- a/native/community-creation/community-configuration.react.js
+++ b/native/community-creation/community-configuration.react.js
@@ -61,7 +61,7 @@
   const [announcementSetting, setAnnouncementSetting] = React.useState(false);
   const [errorMessage, setErrorMessage] = React.useState<?string>();
 
-  const onChangePendingCommunityName = React.useCallback(newValue => {
+  const onChangePendingCommunityName = React.useCallback((newValue: string) => {
     setErrorMessage();
     setPendingCommunityName(newValue);
   }, []);
diff --git a/native/components/emoji-keyboard.react.js b/native/components/emoji-keyboard.react.js
--- a/native/components/emoji-keyboard.react.js
+++ b/native/components/emoji-keyboard.react.js
@@ -31,7 +31,7 @@
   return JSON.parse(recentlyUsedEmojis ?? '[]');
 };
 
-const onStateChangeCallback = nextRecentlyUsedEmojis =>
+const onStateChangeCallback = (nextRecentlyUsedEmojis: $ReadOnlyArray<mixed>) =>
   AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(nextRecentlyUsedEmojis));
 
 const useRecentPicksPersistenceArgs = {
diff --git a/native/components/full-screen-view-modal.react.js b/native/components/full-screen-view-modal.react.js
--- a/native/components/full-screen-view-modal.react.js
+++ b/native/components/full-screen-view-modal.react.js
@@ -40,7 +40,7 @@
   derivedDimensionsInfoSelector,
 } from '../selectors/dimensions-selectors.js';
 import type { NativeMethods } from '../types/react-native.js';
-import type { AnimatedStyleObj } from '../types/styles.js';
+import type { AnimatedViewStyle, ViewStyle } from '../types/styles.js';
 import type { UserProfileBottomSheetNavigationProp } from '../user-profile/user-profile-bottom-sheet-navigator.react.js';
 import {
   clamp,
@@ -175,16 +175,16 @@
 
   closeButton: ?React.ElementRef<TouchableOpacityInstance>;
   mediaIconsContainer: ?React.ElementRef<typeof View>;
-  closeButtonX = new Value(-1);
-  closeButtonY = new Value(-1);
-  closeButtonWidth = new Value(0);
-  closeButtonHeight = new Value(0);
-  closeButtonLastState = new Value(1);
-  mediaIconsX = new Value(-1);
-  mediaIconsY = new Value(-1);
-  mediaIconsWidth = new Value(0);
-  mediaIconsHeight = new Value(0);
-  actionLinksLastState = new Value(1);
+  closeButtonX: Value = new Value(-1);
+  closeButtonY: Value = new Value(-1);
+  closeButtonWidth: Value = new Value(0);
+  closeButtonHeight: Value = new Value(0);
+  closeButtonLastState: Value = new Value(1);
+  mediaIconsX: Value = new Value(-1);
+  mediaIconsY: Value = new Value(-1);
+  mediaIconsWidth: Value = new Value(0);
+  mediaIconsHeight: Value = new Value(0);
+  actionLinksLastState: Value = new Value(1);
 
   centerX: Value;
   centerY: Value;
@@ -959,7 +959,7 @@
     return { width, height: safeAreaHeight };
   }
 
-  get contentViewContainerStyle(): AnimatedStyleObj {
+  get contentViewContainerStyle(): AnimatedViewStyle {
     const { height, width } = this.props.contentDimensions;
     const { height: frameHeight, width: frameWidth } = this.frame;
     const top = (frameHeight - height) / 2 + this.props.dimensions.topInset;
@@ -979,13 +979,13 @@
     };
   }
 
-  static isActive(props) {
+  static isActive(props: Props): boolean {
     const { overlayContext } = props;
     invariant(overlayContext, 'FullScreenViewModal should have OverlayContext');
     return !overlayContext.isDismissing;
   }
 
-  get contentContainerStyle() {
+  get contentContainerStyle(): ViewStyle {
     const { verticalBounds } = this.props.route.params;
     const fullScreenHeight = this.props.dimensions.height;
     const top = verticalBounds.y;
@@ -998,7 +998,7 @@
     return [styles.contentContainer, verticalStyle];
   }
 
-  render() {
+  render(): React.Node {
     const { children, saveContentCallback, copyContentCallback } = this.props;
 
     const statusBar = FullScreenViewModal.isActive(this.props) ? (
diff --git a/native/components/keyboard-avoiding-view.react.js b/native/components/keyboard-avoiding-view.react.js
--- a/native/components/keyboard-avoiding-view.react.js
+++ b/native/components/keyboard-avoiding-view.react.js
@@ -120,7 +120,7 @@
     }
   };
 
-  get relativeKeyboardHeight() {
+  get relativeKeyboardHeight(): number {
     const { viewFrame, keyboardFrame } = this;
     if (!viewFrame || !keyboardFrame) {
       return 0;
@@ -145,7 +145,7 @@
 
   // ESLint doesn't recognize that invariant always throws
   // eslint-disable-next-line consistent-return
-  render() {
+  render(): React.Node {
     const {
       behavior,
       children,
diff --git a/native/components/link-button.react.js b/native/components/link-button.react.js
--- a/native/components/link-button.react.js
+++ b/native/components/link-button.react.js
@@ -29,7 +29,7 @@
   +styles: typeof unboundStyles,
 };
 class LinkButton extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const disabledStyle = this.props.disabled
       ? this.props.styles.disabled
       : null;
diff --git a/native/components/node-height-measurer.react.js b/native/components/node-height-measurer.react.js
--- a/native/components/node-height-measurer.react.js
+++ b/native/components/node-height-measurer.react.js
@@ -495,7 +495,7 @@
     const dummies = currentlyMeasuring.map(({ measureKey, dummy }) => {
       const { children } = dummy.props;
       const style = [dummy.props.style, styles.dummy];
-      const onLayout = event =>
+      const onLayout = (event: LayoutEvent) =>
         this.onDummyLayout(measureKey, iteration, event);
       const node = React.cloneElement(dummy, {
         style,
diff --git a/native/components/selectable-text-input.react.ios.js b/native/components/selectable-text-input.react.ios.js
--- a/native/components/selectable-text-input.react.ios.js
+++ b/native/components/selectable-text-input.react.ios.js
@@ -15,7 +15,7 @@
 import type { SelectionChangeEvent } from '../types/react-native.js';
 
 const SelectableTextInput = React.forwardRef(function BaseSelectableTextInput(
-  props,
+  props: SelectableTextInputProps,
   ref: ReactRefSetter<SelectableTextInputRef>,
 ): React.Node {
   const {
diff --git a/native/components/selectable-text-input.react.js b/native/components/selectable-text-input.react.js
--- a/native/components/selectable-text-input.react.js
+++ b/native/components/selectable-text-input.react.js
@@ -13,7 +13,7 @@
 import type { SelectionChangeEvent } from '../types/react-native.js';
 
 const SelectableTextInput = React.forwardRef(function BaseSelectableTextInput(
-  props,
+  props: SelectableTextInputProps,
   ref: ReactRefSetter<SelectableTextInputRef>,
 ): React.Node {
   const {
diff --git a/native/components/tag-input.react.js b/native/components/tag-input.react.js
--- a/native/components/tag-input.react.js
+++ b/native/components/tag-input.react.js
@@ -377,7 +377,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     return (
       <TouchableOpacity
         onPress={this.onPress}
diff --git a/native/components/thread-list-thread.react.js b/native/components/thread-list-thread.react.js
--- a/native/components/thread-list-thread.react.js
+++ b/native/components/thread-list-thread.react.js
@@ -43,7 +43,7 @@
   +styles: typeof unboundStyles,
 };
 class ThreadListThread extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const { modalIosHighlightUnderlay: underlayColor } = this.props.colors;
 
     return (
diff --git a/native/components/thread-list.react.js b/native/components/thread-list.react.js
--- a/native/components/thread-list.react.js
+++ b/native/components/thread-list.react.js
@@ -50,29 +50,30 @@
   };
   textInput: ?React.ElementRef<typeof TextInput>;
 
-  listDataSelector = createSelector(
-    (propsAndState: PropsAndState) => propsAndState.threadInfos,
-    (propsAndState: PropsAndState) => propsAndState.searchText,
-    (propsAndState: PropsAndState) => propsAndState.searchResults,
-    (propsAndState: PropsAndState) => propsAndState.itemStyle,
-    (propsAndState: PropsAndState) => propsAndState.itemTextStyle,
-    (
-      threadInfos: $ReadOnlyArray<ThreadInfo>,
-      text: string,
-      searchResults: Set<string>,
-    ) =>
-      text
-        ? threadInfos.filter(threadInfo => searchResults.has(threadInfo.id))
-        : // We spread to make sure the result of this selector updates when
-          // any input param (namely itemStyle or itemTextStyle) changes
-          [...threadInfos],
-  );
-
-  get listData() {
+  listDataSelector: PropsAndState => $ReadOnlyArray<ThreadInfo> =
+    createSelector(
+      (propsAndState: PropsAndState) => propsAndState.threadInfos,
+      (propsAndState: PropsAndState) => propsAndState.searchText,
+      (propsAndState: PropsAndState) => propsAndState.searchResults,
+      (propsAndState: PropsAndState) => propsAndState.itemStyle,
+      (propsAndState: PropsAndState) => propsAndState.itemTextStyle,
+      (
+        threadInfos: $ReadOnlyArray<ThreadInfo>,
+        text: string,
+        searchResults: Set<string>,
+      ): $ReadOnlyArray<ThreadInfo> =>
+        text
+          ? threadInfos.filter(threadInfo => searchResults.has(threadInfo.id))
+          : // We spread to make sure the result of this selector updates when
+            // any input param (namely itemStyle or itemTextStyle) changes
+            [...threadInfos],
+    );
+
+  get listData(): $ReadOnlyArray<ThreadInfo> {
     return this.listDataSelector({ ...this.props, ...this.state });
   }
 
-  render() {
+  render(): React.Node {
     let searchBar = null;
     if (this.props.searchIndex) {
       searchBar = (
@@ -103,11 +104,11 @@
 
   static keyExtractor = (
     threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
-  ) => {
+  ): string => {
     return threadInfo.id;
   };
 
-  renderItem = (row: { item: ThreadInfo, ... }) => {
+  renderItem = (row: { +item: ThreadInfo, ... }): React.Node => {
     return (
       <ThreadListThread
         threadInfo={row.item}
@@ -121,7 +122,7 @@
   static getItemLayout = (
     data: ?$ReadOnlyArray<ThreadInfo | MinimallyEncodedThreadInfo>,
     index: number,
-  ) => {
+  ): { length: number, offset: number, index: number } => {
     return { length: 24, offset: 24 * index, index };
   };
 
diff --git a/native/components/user-list-user.react.js b/native/components/user-list-user.react.js
--- a/native/components/user-list-user.react.js
+++ b/native/components/user-list-user.react.js
@@ -49,7 +49,7 @@
   +styles: typeof unboundStyles,
 };
 class UserListUser extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const { userInfo } = this.props;
     let notice = null;
     if (userInfo.notice) {
diff --git a/native/components/user-list.react.js b/native/components/user-list.react.js
--- a/native/components/user-list.react.js
+++ b/native/components/user-list.react.js
@@ -21,7 +21,7 @@
   +indicatorStyle: IndicatorStyle,
 };
 class UserList extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     return (
       <FlatList
         data={this.props.userInfos}
@@ -36,11 +36,11 @@
     );
   }
 
-  static keyExtractor = (userInfo: UserListItem) => {
+  static keyExtractor = (userInfo: UserListItem): string => {
     return userInfo.id;
   };
 
-  renderItem = (row: { item: UserListItem, ... }) => {
+  renderItem = (row: { +item: UserListItem, ... }): React.Node => {
     return (
       <UserListUser
         userInfo={row.item}
@@ -53,7 +53,7 @@
   static getItemLayout = (
     data: ?$ReadOnlyArray<UserListItem>,
     index: number,
-  ) => {
+  ): { length: number, offset: number, index: number } => {
     if (!data) {
       return { length: 0, offset: 0, index };
     }
diff --git a/native/crash.react.js b/native/crash.react.js
--- a/native/crash.react.js
+++ b/native/crash.react.js
@@ -65,9 +65,9 @@
   +doneWaiting: boolean,
 };
 class Crash extends React.PureComponent<Props, State> {
-  errorTitle = _shuffle(errorTitles)[0];
+  errorTitle: string = _shuffle(errorTitles)[0];
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
     this.state = {
       errorReportID: null,
@@ -89,7 +89,7 @@
     this.setState({ doneWaiting: true });
   }
 
-  render() {
+  render(): React.Node {
     const errorText = [...this.props.errorData]
       .reverse()
       .map(errorData => errorData.error.message)
diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js
--- a/native/input/input-state-container.react.js
+++ b/native/input/input-state-container.react.js
@@ -107,6 +107,7 @@
   type PendingMultimediaUploads,
   type MultimediaProcessingStep,
   type MessagePendingUploads,
+  type InputState,
 } from './input-state.js';
 import { encryptMedia } from '../media/encryption-utils.js';
 import { disposeTempFile } from '../media/file-utils.js';
@@ -128,6 +129,7 @@
   [localMessageID: string]: ?$ReadOnlySet<string>,
 };
 type CompletedUploads = $ReadOnly<WritableCompletedUploads>;
+type ActiveURI = { +count: number, +onClear: $ReadOnlyArray<() => mixed> };
 
 type BaseProps = {
   +children: React.Node,
@@ -165,16 +167,16 @@
     pendingUploads: {},
   };
   sendCallbacks: Array<() => void> = [];
-  activeURIs = new Map();
+  activeURIs: Map<string, ActiveURI> = new Map();
   editInputBarCallbacks: Array<
     (params: EditInputBarMessageParameters) => void,
   > = [];
   scrollToMessageCallbacks: Array<(messageID: string) => void> = [];
-  pendingThreadCreations = new Map<string, Promise<string>>();
-  pendingThreadUpdateHandlers = new Map<
+  pendingThreadCreations: Map<string, Promise<string>> = new Map();
+  pendingThreadUpdateHandlers: Map<
     string,
     (ThreadInfo | MinimallyEncodedThreadInfo) => mixed,
-  >();
+  > = new Map();
   // TODO: flip the switch
   // Note that this enables Blob service for encrypted media only
   useBlobServiceUploads = false;
@@ -183,7 +185,7 @@
   // sidebar, the sidebar gets created right away, but the message needs to wait
   // for the uploads to complete before sending. We use this Set to track the
   // message localIDs that need sidebarCreation: true.
-  pendingSidebarCreationMessageLocalIDs = new Set<string>();
+  pendingSidebarCreationMessageLocalIDs: Set<string> = new Set();
 
   static getCompletedUploads(props: Props, state: State): CompletedUploads {
     const completedUploads: WritableCompletedUploads = {};
@@ -297,7 +299,9 @@
     }
   }
 
-  async dispatchMultimediaMessageAction(messageInfo: RawMultimediaMessageInfo) {
+  async dispatchMultimediaMessageAction(
+    messageInfo: RawMultimediaMessageInfo,
+  ): Promise<void> {
     if (!threadIsPending(messageInfo.threadID)) {
       this.props.dispatchActionPromise(
         sendMultimediaMessageActionTypes,
@@ -394,7 +398,7 @@
     }
   }
 
-  inputStateSelector = createSelector(
+  inputStateSelector: State => InputState = createSelector(
     (state: State) => state.pendingUploads,
     (pendingUploads: PendingMultimediaUploads) => ({
       pendingUploads,
@@ -432,7 +436,7 @@
     );
   };
 
-  uploadInProgress = () => {
+  uploadInProgress = (): boolean => {
     if (this.props.ongoingMessageCreation) {
       return true;
     }
@@ -1305,7 +1309,7 @@
     });
   }
 
-  messageHasUploadFailure = (localMessageID: string) => {
+  messageHasUploadFailure = (localMessageID: string): boolean => {
     const pendingUploads = this.state.pendingUploads[localMessageID];
     if (!pendingUploads) {
       return false;
@@ -1352,7 +1356,7 @@
     rawMessageInfo: RawMultimediaMessageInfo,
     localMessageID: string,
     threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
-  ) => {
+  ): Promise<void> => {
     const pendingUploads = this.state.pendingUploads[localMessageID] ?? {};
 
     const now = Date.now();
@@ -1615,7 +1619,10 @@
     }
   };
 
-  waitForCaptureURIUnload(uri: string) {
+  waitForCaptureURIUnload(uri: string): Promise<{
+    +steps: $ReadOnlyArray<MediaMissionStep>,
+    +result: ?string,
+  }> {
     const start = Date.now();
     const path = pathFromURI(uri);
     if (!path) {
@@ -1675,7 +1682,7 @@
     }
   };
 
-  render() {
+  render(): React.Node {
     const inputState = this.inputStateSelector(this.state);
     return (
       <InputStateContext.Provider value={inputState}>
diff --git a/native/keyboard/keyboard-input-host.react.js b/native/keyboard/keyboard-input-host.react.js
--- a/native/keyboard/keyboard-input-host.react.js
+++ b/native/keyboard/keyboard-input-host.react.js
@@ -46,12 +46,12 @@
     }
   }
 
-  static mediaGalleryOpen(props: Props) {
+  static mediaGalleryOpen(props: Props): boolean {
     const { keyboardState } = props;
     return !!(keyboardState && keyboardState.mediaGalleryOpen);
   }
 
-  render() {
+  render(): React.Node {
     const kbComponent = KeyboardInputHost.mediaGalleryOpen(this.props)
       ? mediaGalleryKeyboardName
       : null;
diff --git a/native/media/camera-modal.react.js b/native/media/camera-modal.react.js
--- a/native/media/camera-modal.react.js
+++ b/native/media/camera-modal.react.js
@@ -19,17 +19,21 @@
   PinchGestureHandler,
   TapGestureHandler,
   State as GestureState,
+  type PinchGestureEvent,
+  type TapGestureEvent,
 } from 'react-native-gesture-handler';
 import Orientation from 'react-native-orientation-locker';
 import type { Orientations } from 'react-native-orientation-locker';
 import Reanimated, {
   EasingNode as ReanimatedEasing,
+  type EventResult,
 } from 'react-native-reanimated';
 import { SafeAreaView } from 'react-native-safe-area-context';
 
 import { pathFromURI, filenameFromPathOrURI } from 'lib/media/file-utils.js';
 import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js';
 import type { PhotoCapture } from 'lib/types/media-types.js';
+import type { ReactRef } from 'lib/types/react-types.js';
 import type { Dispatch } from 'lib/types/redux-types.js';
 import { useDispatch } from 'lib/utils/redux-utils.js';
 
@@ -219,6 +223,8 @@
   ]);
 }
 
+type RNCameraStatus = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED';
+
 type TouchableOpacityInstance = React.AbstractComponent<
   React.ElementConfig<typeof TouchableOpacity>,
   NativeMethods,
@@ -260,47 +266,47 @@
 class CameraModal extends React.PureComponent<Props, State> {
   camera: ?RNCamera;
 
-  pinchEvent;
-  pinchHandler = React.createRef();
-  tapEvent;
-  tapHandler = React.createRef();
+  pinchEvent: EventResult<PinchGestureEvent>;
+  pinchHandler: ReactRef<PinchGestureHandler> = React.createRef();
+  tapEvent: EventResult<TapGestureEvent>;
+  tapHandler: ReactRef<TapGestureHandler> = React.createRef();
   animationCode: Node;
 
   closeButton: ?React.ElementRef<TouchableOpacityInstance>;
-  closeButtonX = new Value(-1);
-  closeButtonY = new Value(-1);
-  closeButtonWidth = new Value(0);
-  closeButtonHeight = new Value(0);
+  closeButtonX: Value = new Value(-1);
+  closeButtonY: Value = new Value(-1);
+  closeButtonWidth: Value = new Value(0);
+  closeButtonHeight: Value = new Value(0);
 
   photoButton: ?React.ElementRef<TouchableOpacityInstance>;
-  photoButtonX = new Value(-1);
-  photoButtonY = new Value(-1);
-  photoButtonWidth = new Value(0);
-  photoButtonHeight = new Value(0);
+  photoButtonX: Value = new Value(-1);
+  photoButtonY: Value = new Value(-1);
+  photoButtonWidth: Value = new Value(0);
+  photoButtonHeight: Value = new Value(0);
 
   switchCameraButton: ?React.ElementRef<TouchableOpacityInstance>;
-  switchCameraButtonX = new Value(-1);
-  switchCameraButtonY = new Value(-1);
-  switchCameraButtonWidth = new Value(0);
-  switchCameraButtonHeight = new Value(0);
+  switchCameraButtonX: Value = new Value(-1);
+  switchCameraButtonY: Value = new Value(-1);
+  switchCameraButtonWidth: Value = new Value(0);
+  switchCameraButtonHeight: Value = new Value(0);
 
   flashButton: ?React.ElementRef<TouchableOpacityInstance>;
-  flashButtonX = new Value(-1);
-  flashButtonY = new Value(-1);
-  flashButtonWidth = new Value(0);
-  flashButtonHeight = new Value(0);
+  flashButtonX: Value = new Value(-1);
+  flashButtonY: Value = new Value(-1);
+  flashButtonWidth: Value = new Value(0);
+  flashButtonHeight: Value = new Value(0);
 
-  focusIndicatorX = new Value(-1);
-  focusIndicatorY = new Value(-1);
-  focusIndicatorScale = new Value(0);
-  focusIndicatorOpacity = new Value(0);
+  focusIndicatorX: Value = new Value(-1);
+  focusIndicatorY: Value = new Value(-1);
+  focusIndicatorScale: Value = new Value(0);
+  focusIndicatorOpacity: Value = new Value(0);
 
-  cancelIndicatorAnimation = new Value(0);
+  cancelIndicatorAnimation: Value = new Value(0);
 
   cameraIDsFetched = false;
 
-  stagingModeProgress = new Value(0);
-  sendButtonProgress = new Animated.Value(0);
+  stagingModeProgress: Value = new Value(0);
+  sendButtonProgress: Animated.Value = new Animated.Value(0);
   sendButtonStyle: ViewStyle;
   overlayStyle: AnimatedViewStyle;
 
@@ -502,7 +508,7 @@
     );
   }
 
-  static isActive(props) {
+  static isActive(props: Props): boolean {
     const { overlayContext } = props;
     if (!overlayContext) {
       return true;
@@ -582,7 +588,7 @@
     } catch (e) {}
   }
 
-  get containerStyle() {
+  get containerStyle(): AnimatedViewStyle {
     const { overlayContext } = this.props;
     if (!overlayContext) {
       return styles.container;
@@ -593,7 +599,7 @@
     };
   }
 
-  get focusIndicatorStyle() {
+  get focusIndicatorStyle(): AnimatedViewStyle {
     return {
       ...styles.focusIndicator,
       opacity: this.focusIndicatorOpacity,
@@ -605,7 +611,14 @@
     };
   }
 
-  renderCamera = ({ camera, status }) => {
+  renderCamera = ({
+    camera,
+    status,
+  }: {
+    +camera: RNCamera & { +_cameraHandle?: mixed, ... },
+    status: RNCameraStatus,
+    ...
+  }): React.Node => {
     if (camera && camera._cameraHandle) {
       this.fetchCameraIDs(camera);
     }
@@ -630,7 +643,7 @@
     );
   };
 
-  renderStagingView() {
+  renderStagingView(): React.Node {
     let image = null;
     const { pendingPhotoCapture } = this.state;
     if (pendingPhotoCapture) {
@@ -663,7 +676,7 @@
     );
   }
 
-  renderCameraContent(status) {
+  renderCameraContent(status: RNCameraStatus): React.Node {
     if (status === 'PENDING_AUTHORIZATION') {
       return <ContentLoading fillType="flex" colors={colors.dark} />;
     } else if (status === 'NOT_AUTHORIZED') {
@@ -746,7 +759,7 @@
     );
   }
 
-  render() {
+  render(): React.Node {
     const statusBar = CameraModal.isActive(this.props) ? (
       <ConnectedStatusBar hidden />
     ) : null;
diff --git a/native/media/media-gallery-keyboard.react.js b/native/media/media-gallery-keyboard.react.js
--- a/native/media/media-gallery-keyboard.react.js
+++ b/native/media/media-gallery-keyboard.react.js
@@ -128,7 +128,7 @@
   fetchingPhotos = false;
   flatList: ?FlatList<MediaLibrarySelection>;
   viewableIndices: number[] = [];
-  queueModeProgress = new Animated.Value(0);
+  queueModeProgress: Animated.Value = new Animated.Value(0);
   sendButtonStyle: ViewStyle;
   mediaSelected = false;
 
@@ -153,7 +153,7 @@
     };
   }
 
-  static getDerivedStateFromProps(props: Props) {
+  static getDerivedStateFromProps(props: Props): Partial<State> {
     // We keep this in state since we pass this.state as
     // FlatList's extraData prop
     return { dimensions: props.dimensions };
@@ -161,7 +161,7 @@
 
   componentDidMount() {
     this.mounted = true;
-    return this.fetchPhotos();
+    this.fetchPhotos();
   }
 
   componentWillUnmount() {
@@ -236,7 +236,7 @@
     }
   }
 
-  guardedSetState(change) {
+  guardedSetState(change: Partial<State>) {
     if (this.mounted) {
       this.setState(change);
     }
@@ -427,11 +427,11 @@
     return granted;
   }
 
-  get queueModeActive() {
+  get queueModeActive(): boolean {
     return !!this.state.queuedMediaURIs;
   }
 
-  renderItem = (row: { item: MediaLibrarySelection, ... }) => {
+  renderItem = (row: { +item: MediaLibrarySelection, ... }): React.Node => {
     const { containerHeight, queuedMediaURIs } = this.state;
     invariant(containerHeight, 'should be set');
     const { uri } = row.item;
@@ -452,15 +452,15 @@
     );
   };
 
-  ItemSeparator = () => {
+  ItemSeparator = (): React.Node => {
     return <View style={this.props.styles.separator} />;
   };
 
-  static keyExtractor = (item: MediaLibrarySelection) => {
+  static keyExtractor = (item: MediaLibrarySelection): string => {
     return item.uri;
   };
 
-  GalleryHeader = () => (
+  GalleryHeader = (): React.Node => (
     <View style={this.props.styles.galleryHeader}>
       <Text style={this.props.styles.galleryHeaderTitle}>Photos</Text>
       <Button
@@ -474,7 +474,7 @@
     </View>
   );
 
-  render() {
+  render(): React.Node {
     let content;
     const { selections, error, containerHeight } = this.state;
     const bottomOffsetStyle: ViewStyle = {
diff --git a/native/media/media-utils.js b/native/media/media-utils.js
--- a/native/media/media-utils.js
+++ b/native/media/media-utils.js
@@ -66,7 +66,7 @@
   reportPromise: Promise<$ReadOnlyArray<MediaMissionStep>>,
 } {
   let resolveResult;
-  const sendResult = result => {
+  const sendResult = (result: MediaMissionFailure | MediaResult) => {
     if (resolveResult) {
       resolveResult(result);
     }
diff --git a/native/media/multimedia.react.js b/native/media/multimedia.react.js
--- a/native/media/multimedia.react.js
+++ b/native/media/multimedia.react.js
@@ -42,7 +42,7 @@
   +departingSource: ?Source,
 };
 class Multimedia extends React.PureComponent<Props, State> {
-  static defaultProps = {
+  static defaultProps: Partial<Props> = {
     spinnerColor: 'black',
   };
 
@@ -55,7 +55,7 @@
     };
   }
 
-  get inputState() {
+  get inputState(): InputState {
     const { inputState } = this.props;
     invariant(inputState, 'inputState should be set in Multimedia');
     return inputState;
@@ -101,7 +101,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     const images = [];
     const { currentSource, departingSource } = this.state;
     if (departingSource) {
@@ -117,7 +117,7 @@
     source: Source,
     invisibleLoad?: boolean = false,
     triggerOnLoad?: boolean = true,
-  ) {
+  ): React.Node {
     const onLoadProp = triggerOnLoad ? this.onLoad : undefined;
     if (source.kind === 'encrypted') {
       return (
diff --git a/native/media/remote-image.react.js b/native/media/remote-image.react.js
--- a/native/media/remote-image.react.js
+++ b/native/media/remote-image.react.js
@@ -45,7 +45,7 @@
     }
   }
 
-  render() {
+  render(): React.Node {
     const { style, spinnerColor, invisibleLoad, uri, placeholder } = this.props;
     const source = { uri };
 
diff --git a/native/media/save-media.js b/native/media/save-media.js
--- a/native/media/save-media.js
+++ b/native/media/save-media.js
@@ -136,7 +136,7 @@
   reportPromise: Promise<$ReadOnlyArray<MediaMissionStep>>,
 } {
   let resolveResult;
-  const sendResult = result => {
+  const sendResult = (result: MediaMissionResult) => {
     if (resolveResult) {
       resolveResult(result);
     }
diff --git a/native/media/video-playback-modal.react.js b/native/media/video-playback-modal.react.js
--- a/native/media/video-playback-modal.react.js
+++ b/native/media/video-playback-modal.react.js
@@ -75,6 +75,12 @@
   +item: ChatMultimediaMessageInfoItem,
 };
 
+type ReactNativeVideoOnProgressData = {
+  +currentTime: number,
+  +playableDuration: number,
+  +seekableDuration: number,
+};
+
 type Props = {
   +navigation: AppNavigationProp<'VideoPlaybackModal'>,
   +route: NavigationRoute<'VideoPlaybackModal'>,
@@ -197,7 +203,7 @@
 
   const controlsShowing = useValue(1);
   const outsideButtons = React.useCallback(
-    (x, y) =>
+    (x: Animated.Value, y: Animated.Value) =>
       and(
         or(
           eq(controlsShowing, 0),
@@ -586,13 +592,16 @@
     videoRef.current.seek(0);
   }, []);
 
-  const progressCallback = React.useCallback(res => {
-    setTimeElapsed(formatDuration(res.currentTime));
-    setTotalDuration(formatDuration(res.seekableDuration));
-    setPercentElapsed(
-      Math.ceil((res.currentTime / res.seekableDuration) * 100),
-    );
-  }, []);
+  const progressCallback = React.useCallback(
+    (res: ReactNativeVideoOnProgressData) => {
+      setTimeElapsed(formatDuration(res.currentTime));
+      setTotalDuration(formatDuration(res.seekableDuration));
+      setPercentElapsed(
+        Math.ceil((res.currentTime / res.seekableDuration) * 100),
+      );
+    },
+    [],
+  );
 
   const readyForDisplayCallback = React.useCallback(() => {
     setSpinnerVisible(false);
diff --git a/native/navigation/community-drawer-content.react.js b/native/navigation/community-drawer-content.react.js
--- a/native/navigation/community-drawer-content.react.js
+++ b/native/navigation/community-drawer-content.react.js
@@ -31,6 +31,7 @@
 import {
   flattenDrawerItemsData,
   filterOutThreadAndDescendantIDs,
+  type CommunityDrawerItemDataFlattened,
 } from '../utils/drawer-utils.react.js';
 
 const maxDepth = 2;
@@ -118,7 +119,12 @@
   const navigateToThread = useNavigateToThread();
 
   const renderItem = React.useCallback(
-    ({ item }) => {
+    ({
+      item,
+    }: {
+      +item: CommunityDrawerItemDataFlattened,
+      ...
+    }): React.Node => {
       const isCommunity = threadTypeIsCommunityRoot(item.threadInfo.type);
       return (
         <CommunityDrawerItem
diff --git a/native/navigation/nav-selectors.js b/native/navigation/nav-selectors.js
--- a/native/navigation/nav-selectors.js
+++ b/native/navigation/nav-selectors.js
@@ -46,7 +46,9 @@
 import { useSelector } from '../redux/redux-utils.js';
 import type { NavPlusRedux } from '../types/selector-types.js';
 
-const baseCreateIsForegroundSelector = (routeName: string) =>
+const baseCreateIsForegroundSelector: (
+  routeName: string,
+) => (context: ?NavContextType) => boolean = (routeName: string) =>
   createSelector(
     (context: ?NavContextType) => context && context.state,
     (navigationState: ?PossiblyStaleNavigationState) => {
@@ -73,7 +75,9 @@
   }, [navContext]);
 }
 
-const baseCreateActiveTabSelector = (routeName: string) =>
+const baseCreateActiveTabSelector: (
+  routeName: string,
+) => (context: ?NavContextType) => boolean = (routeName: string) =>
   createSelector(
     (context: ?NavContextType) => context && context.state,
     (navigationState: ?PossiblyStaleNavigationState) => {
diff --git a/native/navigation/orientation-handler.react.js b/native/navigation/orientation-handler.react.js
--- a/native/navigation/orientation-handler.react.js
+++ b/native/navigation/orientation-handler.react.js
@@ -33,7 +33,7 @@
     }
   }
 
-  updateOrientation = orientation => {
+  updateOrientation = (orientation: Orientations) => {
     if (orientation !== this.props.deviceOrientation) {
       this.props.dispatch({
         type: updateDeviceOrientationActionType,
@@ -42,7 +42,7 @@
     }
   };
 
-  render() {
+  render(): React.Node {
     return null;
   }
 }
diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js
--- a/native/navigation/root-navigator.react.js
+++ b/native/navigation/root-navigator.react.js
@@ -10,6 +10,8 @@
   StackNavigationHelpers,
   StackNavigationProp,
   StackRouterOptions,
+  RouteProp,
+  StackCardInterpolationProps,
 } from '@react-navigation/core';
 import {
   createNavigatorFactory,
@@ -93,7 +95,10 @@
     React.useState(true);
   const mergedScreenOptions = React.useMemo(() => {
     if (typeof screenOptions === 'function') {
-      return input => ({
+      return (input: {
+        +route: RouteProp<>,
+        +navigation: RootNavigationHelpers<>,
+      }) => ({
         ...screenOptions(input),
         keyboardHandlingEnabled,
       });
@@ -152,7 +157,7 @@
 });
 const transitionPreset = {
   ...baseTransitionPreset,
-  cardStyleInterpolator: interpolatorProps => {
+  cardStyleInterpolator: (interpolatorProps: StackCardInterpolationProps) => {
     const baseCardStyleInterpolator =
       baseTransitionPreset.cardStyleInterpolator(interpolatorProps);
     const overlayOpacity = interpolatorProps.current.progress.interpolate({
diff --git a/native/navigation/tab-bar.react.js b/native/navigation/tab-bar.react.js
--- a/native/navigation/tab-bar.react.js
+++ b/native/navigation/tab-bar.react.js
@@ -37,7 +37,7 @@
   const prevKeyboardState = prevKeyboardStateRef.current;
 
   const setTabBar = React.useCallback(
-    toValue => {
+    (toValue: number) => {
       const keyboardIsShowing = keyboardState && keyboardState.keyboardShowing;
       const keyboardWasShowing =
         prevKeyboardState && prevKeyboardState.keyboardShowing;
@@ -68,7 +68,7 @@
   const reduxTabBarHeight = useSelector(state => state.dimensions.tabBarHeight);
   const dispatch = useDispatch();
   const setReduxTabBarHeight = React.useCallback(
-    height => {
+    (height: number) => {
       if (height === reduxTabBarHeight) {
         return;
       }
diff --git a/native/navigation/tab-navigator.react.js b/native/navigation/tab-navigator.react.js
--- a/native/navigation/tab-navigator.react.js
+++ b/native/navigation/tab-navigator.react.js
@@ -46,14 +46,14 @@
 const calendarTabOptions = {
   tabBarLabel: 'Calendar',
   // eslint-disable-next-line react/display-name
-  tabBarIcon: ({ color }) => (
+  tabBarIcon: ({ color }: { +color: string, ... }) => (
     <SWMansionIcon name="calendar" style={[styles.icon, { color }]} />
   ),
 };
 const getChatTabOptions = (badge: number) => ({
   tabBarLabel: 'Inbox',
   // eslint-disable-next-line react/display-name
-  tabBarIcon: ({ color }) => (
+  tabBarIcon: ({ color }: { +color: string, ... }) => (
     <SWMansionIcon name="message-square" style={[styles.icon, { color }]} />
   ),
   tabBarBadge: badge ? badge : undefined,
@@ -61,14 +61,14 @@
 const profileTabOptions = {
   tabBarLabel: 'Profile',
   // eslint-disable-next-line react/display-name
-  tabBarIcon: ({ color }) => (
+  tabBarIcon: ({ color }: { +color: string, ... }) => (
     <SWMansionIcon name="user-2" style={[styles.icon, { color }]} />
   ),
 };
 const appsTabOptions = {
   tabBarLabel: 'Apps',
   // eslint-disable-next-line react/display-name
-  tabBarIcon: ({ color }) => (
+  tabBarIcon: ({ color }: { +color: string, ... }) => (
     <SWMansionIcon name="globe-1" style={[styles.icon, { color }]} />
   ),
 };
diff --git a/native/profile/appearance-preferences.react.js b/native/profile/appearance-preferences.react.js
--- a/native/profile/appearance-preferences.react.js
+++ b/native/profile/appearance-preferences.react.js
@@ -93,7 +93,7 @@
   +colors: Colors,
 };
 class AppearancePreferences extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const { panelIosHighlightUnderlay: underlay } = this.props.colors;
 
     const options: Array<React.Node> = [];
diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js
--- a/native/profile/backup-menu.react.js
+++ b/native/profile/backup-menu.react.js
@@ -48,7 +48,7 @@
   }, [restoreBackupProtocol, userStore]);
 
   const onBackupToggled = React.useCallback(
-    value => {
+    (value: boolean) => {
       dispatch({
         type: setLocalSettingsActionType,
         payload: { isBackupEnabled: value },
diff --git a/native/profile/custom-server-modal.react.js b/native/profile/custom-server-modal.react.js
--- a/native/profile/custom-server-modal.react.js
+++ b/native/profile/custom-server-modal.react.js
@@ -77,7 +77,7 @@
     };
   }
 
-  render() {
+  render(): React.Node {
     return (
       <Modal
         containerStyle={this.props.styles.container}
diff --git a/native/profile/default-notifications-preferences.react.js b/native/profile/default-notifications-preferences.react.js
--- a/native/profile/default-notifications-preferences.react.js
+++ b/native/profile/default-notifications-preferences.react.js
@@ -143,7 +143,7 @@
     this.selectNotificationSetting(notificationTypes.BADGE_ONLY);
   };
 
-  render() {
+  render(): React.Node {
     const { styles, selectedDefaultNotification } = this.props;
     return (
       <ScrollView
diff --git a/native/profile/dev-tools.react.js b/native/profile/dev-tools.react.js
--- a/native/profile/dev-tools.react.js
+++ b/native/profile/dev-tools.react.js
@@ -102,7 +102,7 @@
   +dispatch: Dispatch,
 };
 class DevTools extends React.PureComponent<Props> {
-  render() {
+  render(): React.Node {
     const { panelIosHighlightUnderlay: underlay } = this.props.colors;
 
     const serverButtons: Array<React.Node> = [];
@@ -206,7 +206,7 @@
     );
   }
 
-  onPressCrash = () => {
+  onPressCrash = (): empty => {
     throw new Error('User triggered crash through dev menu!');
   };
 
diff --git a/native/profile/edit-password.react.js b/native/profile/edit-password.react.js
--- a/native/profile/edit-password.react.js
+++ b/native/profile/edit-password.react.js
@@ -129,7 +129,7 @@
     this.mounted = false;
   }
 
-  render() {
+  render(): React.Node {
     const buttonContent =
       this.props.loadingStatus === 'loading' ? (
         <ActivityIndicator size="small" color="white" />
diff --git a/native/profile/emoji-user-avatar-creation.react.js b/native/profile/emoji-user-avatar-creation.react.js
--- a/native/profile/emoji-user-avatar-creation.react.js
+++ b/native/profile/emoji-user-avatar-creation.react.js
@@ -5,6 +5,7 @@
 
 import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js';
 import { savedEmojiAvatarSelectorForCurrentUser } from 'lib/selectors/user-selectors.js';
+import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js';
 
 import type { ProfileNavigationProp } from './profile.react.js';
 import { useNativeSetUserAvatar } from '../avatars/avatar-hooks.js';
@@ -27,7 +28,7 @@
   const nativeSetUserAvatar = useNativeSetUserAvatar();
 
   const setAvatar = React.useCallback(
-    async avatarRequest => {
+    async (avatarRequest: UpdateUserAvatarRequest): Promise<void> => {
       const result = await nativeSetUserAvatar(avatarRequest);
       displayActionResultModal('Avatar updated!');
       return result;
diff --git a/native/profile/keyserver-selection-list.react.js b/native/profile/keyserver-selection-list.react.js
--- a/native/profile/keyserver-selection-list.react.js
+++ b/native/profile/keyserver-selection-list.react.js
@@ -16,7 +16,12 @@
   return `${item.keyserverAdminUserInfo.id}${item.keyserverInfo.urlPrefix}`;
 }
 
-function renderKeyserverListItem({ item }) {
+function renderKeyserverListItem({
+  item,
+}: {
+  +item: SelectedKeyserverInfo,
+  ...
+}) {
   return <KeyserverSelectionListItem {...item} />;
 }
 
diff --git a/native/profile/profile-screen.react.js b/native/profile/profile-screen.react.js
--- a/native/profile/profile-screen.react.js
+++ b/native/profile/profile-screen.react.js
@@ -162,7 +162,7 @@
 };
 
 class ProfileScreen extends React.PureComponent<Props> {
-  get loggedOutOrLoggingOut() {
+  get loggedOutOrLoggingOut(): boolean {
     return (
       !this.props.currentUserInfo ||
       this.props.currentUserInfo.anonymous ||
@@ -170,7 +170,7 @@
     );
   }
 
-  render() {
+  render(): React.Node {
     let developerTools,
       defaultNotifications,
       keyserverSelection,
diff --git a/native/profile/profile.react.js b/native/profile/profile.react.js
--- a/native/profile/profile.react.js
+++ b/native/profile/profile.react.js
@@ -4,6 +4,7 @@
   StackNavigationProp,
   StackNavigationHelpers,
   StackHeaderProps,
+  StackHeaderLeftButtonProps,
 } from '@react-navigation/core';
 import { createStackNavigator } from '@react-navigation/stack';
 import * as React from 'react';
@@ -107,7 +108,7 @@
   const colors = useColors();
 
   const headerLeftButton = React.useCallback(
-    headerProps =>
+    (headerProps: StackHeaderLeftButtonProps) =>
       headerProps.canGoBack ? (
         <HeaderBackButton {...headerProps} />
       ) : (
diff --git a/native/profile/relationship-list-item.react.js b/native/profile/relationship-list-item.react.js
--- a/native/profile/relationship-list-item.react.js
+++ b/native/profile/relationship-list-item.react.js
@@ -10,6 +10,7 @@
 } from 'lib/actions/relationship-actions.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import type { LoadingStatus } from 'lib/types/loading-types.js';
+import type { ReactRef } from 'lib/types/react-types.js';
 import {
   type RelationshipRequest,
   type RelationshipAction,
@@ -118,9 +119,9 @@
   +navigateToUserProfileBottomSheet: (userID: string) => mixed,
 };
 class RelationshipListItem extends React.PureComponent<Props> {
-  editButton = React.createRef<React.ElementRef<typeof View>>();
+  editButton: ReactRef<React.ElementRef<typeof View>> = React.createRef();
 
-  render() {
+  render(): React.Node {
     const {
       lastListItem,
       removeUserLoadingStatus,
@@ -230,7 +231,7 @@
     this.props.onSelect({ id, username });
   };
 
-  visibleEntryIDs() {
+  visibleEntryIDs(): [string] {
     const { relationshipListRoute } = this.props;
     const id = {
       [FriendListRouteName]: 'unfriend',
@@ -300,7 +301,9 @@
     );
   }
 
-  async updateFriendship(action: RelationshipAction) {
+  async updateFriendship(
+    action: RelationshipAction,
+  ): Promise<RelationshipErrors> {
     try {
       return await this.props.updateRelationships({
         action,
diff --git a/native/profile/relationship-list.react.js b/native/profile/relationship-list.react.js
--- a/native/profile/relationship-list.react.js
+++ b/native/profile/relationship-list.react.js
@@ -96,7 +96,9 @@
   const callSearchUsers = useServerCall(searchUsers);
   const userInfos = useSelector(state => state.userStore.userInfos);
   const searchUsersOnServer = React.useCallback(
-    async (usernamePrefix: string) => {
+    async (
+      usernamePrefix: string,
+    ): Promise<$ReadOnlyArray<GlobalAccountUserInfo>> => {
       if (usernamePrefix.length === 0) {
         return [];
       }
diff --git a/native/profile/toggle-report.react.js b/native/profile/toggle-report.react.js
--- a/native/profile/toggle-report.react.js
+++ b/native/profile/toggle-report.react.js
@@ -17,7 +17,7 @@
   const isReportEnabled = useIsReportEnabled(reportType);
 
   const onReportToggled = React.useCallback(
-    value => {
+    (value: boolean) => {
       dispatch({
         type: updateReportsEnabledActionType,
         payload: { [(reportType: string)]: value },
diff --git a/native/push/push-handler.react.js b/native/push/push-handler.react.js
--- a/native/push/push-handler.react.js
+++ b/native/push/push-handler.react.js
@@ -420,7 +420,7 @@
     }
   }
 
-  requestAndroidNotificationsPermission = () => {
+  requestAndroidNotificationsPermission = (): Promise<boolean> => {
     if (!this.androidNotificationsPermissionPromise) {
       this.androidNotificationsPermissionPromise = (async () => {
         const notifPermission =
@@ -675,7 +675,7 @@
   handleAndroidNotificationIfActive = (
     threadID: string,
     texts: { body: string, title: ?string },
-  ) => {
+  ): boolean => {
     if (this.currentState !== 'active') {
       return false;
     }
@@ -683,7 +683,7 @@
     return true;
   };
 
-  render() {
+  render(): React.Node {
     return (
       <InAppNotification
         {...this.state.inAppNotifProps}
diff --git a/native/redux/client-db-utils.js b/native/redux/client-db-utils.js
--- a/native/redux/client-db-utils.js
+++ b/native/redux/client-db-utils.js
@@ -70,10 +70,13 @@
   );
 
   // Convert `rawThreadInfo`s to a map of `threadID` => `threadInfo`.
-  const threadIDToThreadInfo = rawThreadInfos.reduce((acc, threadInfo) => {
-    acc[threadInfo.id] = threadInfo;
-    return acc;
-  }, {});
+  const threadIDToThreadInfo = rawThreadInfos.reduce(
+    (acc: { [string]: RawThreadInfo }, threadInfo: RawThreadInfo) => {
+      acc[threadInfo.id] = threadInfo;
+      return acc;
+    },
+    {},
+  );
 
   // Apply `migrationFunc` to `threadInfo`s.
   const updatedThreadIDToThreadInfo: ThreadStoreThreadInfos =
diff --git a/native/redux/connectivity-updater.react.js b/native/redux/connectivity-updater.react.js
--- a/native/redux/connectivity-updater.react.js
+++ b/native/redux/connectivity-updater.react.js
@@ -8,12 +8,23 @@
 import { updateConnectivityActiveType } from './action-types.js';
 import { useSelector } from './redux-utils.js';
 
+type NetInfoStateType =
+  | 'none'
+  | 'unknown'
+  | 'cellular'
+  | 'wifi'
+  | 'bluetooth'
+  | 'ethernet'
+  | 'wimax'
+  | 'vpn'
+  | 'other';
+
 export default function ConnectivityUpdater(): null {
   const connectivity = useSelector(state => state.connectivity);
   const dispatch = useDispatch();
 
   const onConnectionChange = React.useCallback(
-    ({ type }) => {
+    ({ type }: { +type: NetInfoStateType, ... }) => {
       const connected = type !== 'none' && type !== 'unknown';
       const hasWiFi = type === 'wifi';
       if (
diff --git a/native/redux/persist.js b/native/redux/persist.js
--- a/native/redux/persist.js
+++ b/native/redux/persist.js
@@ -118,7 +118,7 @@
     ...state,
     messageSentFromRoute: [],
   }),
-  [3]: state => ({
+  [3]: (state: any) => ({
     currentUserInfo: state.currentUserInfo,
     entryStore: state.entryStore,
     threadInfos: state.threadInfos,
@@ -145,7 +145,7 @@
     ...state,
     calendarFilters: defaultCalendarFilters,
   }),
-  [6]: state => ({
+  [6]: (state: any) => ({
     ...state,
     threadInfos: undefined,
     threadStore: {
@@ -153,7 +153,7 @@
       inconsistencyResponses: [],
     },
   }),
-  [7]: state => ({
+  [7]: (state: AppState) => ({
     ...state,
     lastUserInteraction: undefined,
     sessionID: undefined,
@@ -176,14 +176,14 @@
       actualizedCalendarQuery: undefined,
     },
   }),
-  [9]: state => ({
+  [9]: (state: any) => ({
     ...state,
     connection: {
       ...state.connection,
       lateResponses: [],
     },
   }),
-  [10]: state => ({
+  [10]: (state: any) => ({
     ...state,
     nextLocalID: highestLocalIDSelector(state) + 1,
     connection: {
@@ -211,7 +211,7 @@
     deviceOrientation: Orientation.getInitialOrientation(),
   }),
   [14]: (state: AppState) => state,
-  [15]: state => ({
+  [15]: (state: any) => ({
     ...state,
     threadStore: {
       ...state.threadStore,
@@ -229,7 +229,7 @@
     },
     queuedReports: [],
   }),
-  [16]: state => {
+  [16]: (state: any) => {
     const result = {
       ...state,
       messageSentFromRoute: undefined,
@@ -243,7 +243,7 @@
     }
     return result;
   },
-  [17]: state => ({
+  [17]: (state: any) => ({
     ...state,
     userInfos: undefined,
     userStore: {
@@ -251,14 +251,14 @@
       inconsistencyResponses: [],
     },
   }),
-  [18]: state => ({
+  [18]: (state: AppState) => ({
     ...state,
     userStore: {
       userInfos: state.userStore.userInfos,
       inconsistencyReports: [],
     },
   }),
-  [19]: state => {
+  [19]: (state: any) => {
     const threadInfos: { [string]: RawThreadInfo } = {};
     for (const threadID in state.threadStore.threadInfos) {
       const threadInfo = state.threadStore.threadInfos[threadID];
@@ -286,7 +286,7 @@
       messageTypes.SIDEBAR_SOURCE,
     ]),
   }),
-  [22]: state => {
+  [22]: (state: any) => {
     for (const key in state.drafts) {
       const value = state.drafts[key];
       try {
@@ -302,19 +302,19 @@
       drafts: undefined,
     };
   },
-  [23]: state => ({
+  [23]: (state: AppState) => ({
     ...state,
     globalThemeInfo: defaultGlobalThemeInfo,
   }),
-  [24]: state => ({
+  [24]: (state: AppState) => ({
     ...state,
     enabledApps: defaultEnabledApps,
   }),
-  [25]: state => ({
+  [25]: (state: AppState) => ({
     ...state,
     crashReportsEnabled: __DEV__,
   }),
-  [26]: state => {
+  [26]: (state: any) => {
     const { currentUserInfo } = state;
     if (currentUserInfo.anonymous) {
       return state;
@@ -333,7 +333,7 @@
       },
     };
   },
-  [27]: state => ({
+  [27]: (state: any) => ({
     ...state,
     queuedReports: undefined,
     enabledReports: undefined,
@@ -358,7 +358,7 @@
       ],
     },
   }),
-  [28]: state => {
+  [28]: (state: AppState) => {
     const threadParentToChildren: { [string]: string[] } = {};
     for (const threadID in state.threadStore.threadInfos) {
       const threadInfo = state.threadStore.threadInfos[threadID];
@@ -463,7 +463,7 @@
   },
   [32]: (state: AppState) => unshimClientDB(state, [messageTypes.MULTIMEDIA]),
   [33]: (state: AppState) => unshimClientDB(state, [messageTypes.REACTION]),
-  [34]: state => {
+  [34]: (state: any) => {
     const { threadIDsToNotifIDs, ...stateSansThreadIDsToNotifIDs } = state;
     return stateSansThreadIDsToNotifIDs;
   },
@@ -523,7 +523,7 @@
 
     // 8. Convert rawThreadInfos to a map of threadID to threadInfo
     const threadIDToThreadInfo = rawThreadInfosWithPinnedCount.reduce(
-      (acc, threadInfo) => {
+      (acc: { [string]: RawThreadInfo }, threadInfo: RawThreadInfo) => {
         acc[threadInfo.id] = threadInfo;
         return acc;
       },
@@ -568,7 +568,7 @@
 
     return state;
   },
-  [37]: state => {
+  [37]: (state: AppState) => {
     const operations = messageStoreOpsHandlers.convertOpsToClientDBOps([
       {
         type: 'remove_all_threads',
@@ -591,10 +591,10 @@
 
     return state;
   },
-  [38]: state =>
+  [38]: (state: AppState) =>
     updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions),
   [39]: (state: AppState) => unshimClientDB(state, [messageTypes.EDIT_MESSAGE]),
-  [40]: state =>
+  [40]: (state: AppState) =>
     updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions),
   [41]: (state: AppState) => {
     const queuedReports = state.reportStore.queuedReports.map(report => ({
@@ -624,7 +624,7 @@
     }
     return state;
   },
-  [43]: async state => {
+  [43]: async (state: any) => {
     const { messages, drafts, threads, messageStoreThreads } =
       await commCoreModule.getClientDBStore();
 
@@ -677,7 +677,7 @@
       inviteLinksStore: convertInviteLinksStoreToNewIDSchema(inviteLinksStore),
     };
   },
-  [44]: async state => {
+  [44]: async (state: any) => {
     const { cookie, ...rest } = state;
 
     return {
@@ -685,7 +685,7 @@
       keyserverStore: { keyserverInfos: { [ashoatKeyserverID]: { cookie } } },
     };
   },
-  [45]: async state => {
+  [45]: async (state: any) => {
     const { updatesCurrentAsOf, keyserverStore, ...rest } = state;
 
     return {
@@ -702,7 +702,7 @@
       },
     };
   },
-  [46]: async state => {
+  [46]: async (state: AppState) => {
     const { currentAsOf } = state.messageStore;
 
     return {
@@ -713,7 +713,7 @@
       },
     };
   },
-  [47]: async state => {
+  [47]: async (state: any) => {
     const { urlPrefix, keyserverStore, ...rest } = state;
 
     return {
@@ -730,7 +730,7 @@
       },
     };
   },
-  [48]: async state => {
+  [48]: async (state: any) => {
     const { connection, keyserverStore, ...rest } = state;
 
     return {
@@ -747,7 +747,7 @@
       },
     };
   },
-  [49]: async state => {
+  [49]: async (state: AppState) => {
     const { keyserverStore, ...rest } = state;
 
     const { connection, ...keyserverRest } =
@@ -767,7 +767,7 @@
       connection,
     };
   },
-  [50]: async state => {
+  [50]: async (state: any) => {
     const { connection, ...rest } = state;
     const { actualizedCalendarQuery, ...connectionRest } = connection;
 
@@ -777,7 +777,7 @@
       actualizedCalendarQuery,
     };
   },
-  [51]: async state => {
+  [51]: async (state: any) => {
     const { lastCommunicatedPlatformDetails, keyserverStore, ...rest } = state;
 
     return {
@@ -794,14 +794,14 @@
       },
     };
   },
-  [52]: async state => ({
+  [52]: async (state: AppState) => ({
     ...state,
     integrityStore: {
       threadHashes: {},
       threadHashingStatus: 'data_not_loaded',
     },
   }),
-  [53]: state => {
+  [53]: (state: any) => {
     if (!state.userStore.inconsistencyReports) {
       return state;
     }
@@ -833,7 +833,7 @@
       },
     };
   },
-  [54]: state => {
+  [54]: (state: any) => {
     let updatedMessageStoreThreads: MessageStoreThreads = {};
     for (const threadID: string in state.messageStore.threads) {
       const { lastNavigatedTo, lastPruned, ...rest } =
@@ -853,7 +853,7 @@
       },
     };
   },
-  [55]: async state =>
+  [55]: async (state: AppState) =>
     __DEV__
       ? {
           ...state,
@@ -869,7 +869,7 @@
           },
         }
       : state,
-  [56]: state => {
+  [56]: (state: any) => {
     const { deviceToken, keyserverStore, ...rest } = state;
 
     return {
@@ -886,7 +886,7 @@
       },
     };
   },
-  [57]: async state => {
+  [57]: async (state: any) => {
     const {
       // eslint-disable-next-line no-unused-vars
       connection,
@@ -926,15 +926,18 @@
     }
     return state;
   },
-  [59]: state => {
+  [59]: (state: AppState) => {
     const clientDBThreadInfos = commCoreModule.getAllThreadsSync();
     const rawThreadInfos = clientDBThreadInfos.map(
       convertClientDBThreadInfoToRawThreadInfo,
     );
-    const rawThreadInfosObject = rawThreadInfos.reduce((acc, threadInfo) => {
-      acc[threadInfo.id] = threadInfo;
-      return acc;
-    }, {});
+    const rawThreadInfosObject = rawThreadInfos.reduce(
+      (acc: { [string]: RawThreadInfo }, threadInfo: RawThreadInfo) => {
+        acc[threadInfo.id] = threadInfo;
+        return acc;
+      },
+      {},
+    );
 
     const migratedRawThreadInfos =
       persistMigrationToRemoveSelectRolePermissions(rawThreadInfosObject);
@@ -964,7 +967,7 @@
 
     return state;
   },
-  [60]: state =>
+  [60]: (state: AppState) =>
     updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions),
 };
 
diff --git a/native/redux/update-roles-and-permissions.js b/native/redux/update-roles-and-permissions.js
--- a/native/redux/update-roles-and-permissions.js
+++ b/native/redux/update-roles-and-permissions.js
@@ -32,7 +32,7 @@
     ];
   }
 
-  const constructNodes = nodeID => ({
+  const constructNodes = (nodeID: string): ThreadTraversalNode => ({
     threadID: nodeID,
     children: parentThreadMap[nodeID]?.map(constructNodes) ?? null,
   });
diff --git a/native/roles/roles-navigator.react.js b/native/roles/roles-navigator.react.js
--- a/native/roles/roles-navigator.react.js
+++ b/native/roles/roles-navigator.react.js
@@ -3,6 +3,7 @@
 import type {
   StackNavigationProp,
   StackNavigationHelpers,
+  StackHeaderLeftButtonProps,
 } from '@react-navigation/core';
 import { createStackNavigator } from '@react-navigation/stack';
 import * as React from 'react';
@@ -34,7 +35,7 @@
 const communityRolesScreenOptions = {
   headerTitle: 'Create role',
   // eslint-disable-next-line react/display-name
-  headerLeft: headerLeftProps => (
+  headerLeft: (headerLeftProps: StackHeaderLeftButtonProps) => (
     <CommunityRolesHeaderLeftButton {...headerLeftProps} />
   ),
 };
diff --git a/native/search/message-search.react.js b/native/search/message-search.react.js
--- a/native/search/message-search.react.js
+++ b/native/search/message-search.react.js
@@ -147,7 +147,7 @@
   }, []);
 
   const renderItem = React.useCallback(
-    ({ item }) => {
+    ({ item }: { +item: ChatMessageItemWithHeight, ... }) => {
       if (item.itemType === 'loader') {
         return <ListLoadingIndicator />;
       }
diff --git a/native/tooltip/tooltip.react.js b/native/tooltip/tooltip.react.js
--- a/native/tooltip/tooltip.react.js
+++ b/native/tooltip/tooltip.react.js
@@ -36,7 +36,11 @@
   type LayoutCoordinates,
 } from '../types/layout-types.js';
 import type { LayoutEvent } from '../types/react-native.js';
-import { AnimatedView } from '../types/styles.js';
+import {
+  AnimatedView,
+  type ViewStyle,
+  type AnimatedViewStyle,
+} from '../types/styles.js';
 
 /* eslint-disable import/no-named-as-default-member */
 const { Value, Node, Extrapolate, add, multiply, interpolateNode } = Animated;
@@ -258,14 +262,14 @@
       return 'below';
     }
 
-    get opacityStyle() {
+    get opacityStyle(): AnimatedViewStyle {
       return {
         ...this.props.styles.backdrop,
         opacity: this.backdropOpacity,
       };
     }
 
-    get contentContainerStyle() {
+    get contentContainerStyle(): ViewStyle {
       const { verticalBounds } = this.props.route.params;
       const fullScreenHeight = this.props.dimensions.height;
       const top = verticalBounds.y;
@@ -278,7 +282,7 @@
       };
     }
 
-    get buttonStyle() {
+    get buttonStyle(): ViewStyle {
       const { params } = this.props.route;
       const { initialCoordinates, verticalBounds } = params;
       const { x, y, width, height } = initialCoordinates;
@@ -290,14 +294,14 @@
       };
     }
 
-    get margin() {
+    get margin(): number {
       const customMargin = this.props.route.params.margin;
       return customMargin !== null && customMargin !== undefined
         ? customMargin
         : 20;
     }
 
-    get tooltipContainerStyle() {
+    get tooltipContainerStyle(): AnimatedViewStyle {
       const { dimensions, route } = this.props;
       const { initialCoordinates, verticalBounds, chatInputBarHeight } =
         route.params;
@@ -356,7 +360,7 @@
       return style;
     }
 
-    render() {
+    render(): React.Node {
       const {
         dimensions,
         overlayContext,
@@ -473,7 +477,7 @@
       );
     }
 
-    getTooltipItem() {
+    getTooltipItem(): React.ComponentType<TooltipItemBaseProps> {
       const BoundTooltipItem = this.props.boundTooltipItem;
       return BoundTooltipItem;
     }
@@ -483,7 +487,7 @@
       this.props.tooltipContext.showActionSheet();
     };
 
-    renderMoreIcon = () => {
+    renderMoreIcon = (): React.Node => {
       const { styles } = this.props;
       return (
         <SWMansionIcon name="menu-vertical" style={styles.icon} size={16} />
@@ -507,7 +511,12 @@
       }
     };
   }
-  function ConnectedTooltip(props) {
+  function ConnectedTooltip(
+    props: $ReadOnly<{
+      ...BaseTooltipPropsType,
+      +hideTooltip: () => mixed,
+    }>,
+  ) {
     const dimensions = useSelector(state => state.dimensions);
     const overlayContext = React.useContext(OverlayContext);
     const chatContext = React.useContext(ChatContext);
@@ -528,7 +537,7 @@
 
     const styles = useStyles(unboundStyles);
     const boundTooltipItem = React.useCallback(
-      innerProps => {
+      (innerProps: TooltipItemBaseProps) => {
         const containerStyle = isFixed
           ? [styles.itemContainer, styles.itemContainerFixed]
           : styles.itemContainer;
diff --git a/native/utils/typeahead-utils.js b/native/utils/typeahead-utils.js
--- a/native/utils/typeahead-utils.js
+++ b/native/utils/typeahead-utils.js
@@ -18,20 +18,34 @@
   `((^(.|\n)*\\s+)|^)@(${validChatNameRegexString})?$`,
 );
 
+type FocusAndUpdateTextAndSelection = (
+  text: string,
+  selection: Selection,
+) => void;
+
 export type TypeaheadTooltipActionsParams<SuggestionItemType> = {
   +suggestions: $ReadOnlyArray<SuggestionItemType>,
   +textBeforeAtSymbol: string,
   +text: string,
   +query: string,
-  +focusAndUpdateTextAndSelection: (text: string, selection: Selection) => void,
+  +focusAndUpdateTextAndSelection: FocusAndUpdateTextAndSelection,
+};
+
+type MentionTypeaheadTooltipActionExecuteHandlerParams = {
+  +textBeforeAtSymbol: string,
+  +text: string,
+  +query: string,
+  +mentionText: string,
+  +focusAndUpdateTextAndSelection: FocusAndUpdateTextAndSelection,
 };
+
 function mentionTypeaheadTooltipActionExecuteHandler({
   textBeforeAtSymbol,
   text,
   query,
   mentionText,
   focusAndUpdateTextAndSelection,
-}) {
+}: MentionTypeaheadTooltipActionExecuteHandlerParams) {
   const { newText, newSelectionStart } = getNewTextAndSelection(
     textBeforeAtSymbol,
     text,
@@ -43,6 +57,7 @@
     end: newSelectionStart,
   });
 }
+
 function mentionTypeaheadTooltipActions({
   suggestions,
   textBeforeAtSymbol,