diff --git a/native/account/registration/keyserver-selection.react.js b/native/account/registration/keyserver-selection.react.js
--- a/native/account/registration/keyserver-selection.react.js
+++ b/native/account/registration/keyserver-selection.react.js
@@ -2,7 +2,7 @@
 
 import invariant from 'invariant';
 import * as React from 'react';
-import { Text, View } from 'react-native';
+import { Text, View, TextInput } from 'react-native';
 
 import {
   useGetVersion,
@@ -59,7 +59,8 @@
   const [customKeyserver, setCustomKeyserver] = React.useState(
     initialKeyserverURL === defaultURLPrefix ? '' : initialKeyserverURL,
   );
-  const customKeyserverTextInputRef = React.useRef();
+  const customKeyserverTextInputRef =
+    React.useRef<?React.ElementRef<typeof TextInput>>();
 
   let initialSelection;
   if (initialKeyserverURL === defaultURLPrefix) {
diff --git a/native/account/registration/password-selection.react.js b/native/account/registration/password-selection.react.js
--- a/native/account/registration/password-selection.react.js
+++ b/native/account/registration/password-selection.react.js
@@ -2,7 +2,7 @@
 
 import invariant from 'invariant';
 import * as React from 'react';
-import { View, Text, Platform } from 'react-native';
+import { View, Text, Platform, TextInput } from 'react-native';
 
 import sleep from 'lib/utils/sleep.js';
 
@@ -112,7 +112,8 @@
     errorText = <Text style={styles.errorText}>Password cannot be empty</Text>;
   }
 
-  const confirmPasswordInputRef = React.useRef();
+  const confirmPasswordInputRef =
+    React.useRef<?React.ElementRef<typeof TextInput>>();
   const focusConfirmPasswordInput = React.useCallback(() => {
     confirmPasswordInputRef.current?.focus();
   }, []);
@@ -136,7 +137,7 @@
     [confirmPasswordEmpty],
   );
 
-  const passwordInputRef = React.useRef();
+  const passwordInputRef = React.useRef<?React.ElementRef<typeof TextInput>>();
   const passwordLength = password.length;
   const onChangePasswordInput = React.useCallback(
     (input: string) => {
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
@@ -145,7 +145,7 @@
     },
     [onSuccessfulWalletSignature, onClosing, closeBottomSheet],
   );
-  const prevClosingRef = React.useRef();
+  const prevClosingRef = React.useRef<?boolean>();
   React.useEffect(() => {
     if (closing && !prevClosingRef.current) {
       closeBottomSheet?.();
diff --git a/native/chat/chat-context.js b/native/chat/chat-context.js
--- a/native/chat/chat-context.js
+++ b/native/chat/chat-context.js
@@ -40,7 +40,7 @@
   const chatContext = React.useContext(ChatContext);
   invariant(chatContext, 'Chat context should be set');
 
-  const measureRegistrationRef = React.useRef();
+  const measureRegistrationRef = React.useRef<?RegisteredMeasurer>();
   if (!measureRegistrationRef.current) {
     measureRegistrationRef.current = chatContext.registerMeasurer();
   }
diff --git a/native/chat/chat-thread-list.react.js b/native/chat/chat-thread-list.react.js
--- a/native/chat/chat-thread-list.react.js
+++ b/native/chat/chat-thread-list.react.js
@@ -17,6 +17,7 @@
   Platform,
   TouchableWithoutFeedback,
   BackHandler,
+  TextInput,
 } from 'react-native';
 import { FloatingAction } from 'react-native-floating-action';
 
@@ -111,7 +112,7 @@
   }, []);
 
   const scrollPos = React.useRef(0);
-  const flatListRef = React.useRef();
+  const flatListRef = React.useRef<?FlatList<Item>>();
 
   const onScroll = React.useCallback(
     (event: ScrollEvent) => {
@@ -174,7 +175,7 @@
     clearSearch();
   }, [clearSearch, onChangeSearchText]);
 
-  const searchInputRef = React.useRef();
+  const searchInputRef = React.useRef<?React.ElementRef<typeof TextInput>>();
 
   const onPressItem = React.useCallback(
     (threadInfo: ThreadInfo, pendingPersonalThreadUserInfo?: UserInfo) => {
diff --git a/native/chat/compose-subchannel.react.js b/native/chat/compose-subchannel.react.js
--- a/native/chat/compose-subchannel.react.js
+++ b/native/chat/compose-subchannel.react.js
@@ -29,7 +29,10 @@
 import { useNavigateToThread } from './message-list-types.js';
 import ParentThreadHeader from './parent-thread-header.react.js';
 import LinkButton from '../components/link-button.react.js';
-import { createTagInput } from '../components/tag-input.react.js';
+import {
+  createTagInput,
+  type BaseTagInput,
+} from '../components/tag-input.react.js';
 import ThreadList from '../components/thread-list.react.js';
 import UserList from '../components/user-list.react.js';
 import { useCalendarQuery } from '../navigation/nav-selectors.js';
@@ -65,7 +68,7 @@
   const [createButtonEnabled, setCreateButtonEnabled] =
     React.useState<boolean>(true);
 
-  const tagInputRef = React.useRef();
+  const tagInputRef = React.useRef<?BaseTagInput<AccountUserInfo>>();
   const onUnknownErrorAlertAcknowledged = React.useCallback(() => {
     setUsernameInputText('');
     tagInputRef.current?.focus();
diff --git a/native/chat/message-editing-context-provider.react.js b/native/chat/message-editing-context-provider.react.js
--- a/native/chat/message-editing-context-provider.react.js
+++ b/native/chat/message-editing-context-provider.react.js
@@ -20,7 +20,7 @@
 function MessageEditingContextProvider(props: Props): React.Node {
   const [editState, setEditState] = React.useState<EditState>(defaultEditState);
 
-  const pendingCallbacksRef = React.useRef([]);
+  const pendingCallbacksRef = React.useRef<Array<() => void>>([]);
 
   const setEditedMessage = React.useCallback(
     (editedMessage: ?MessageInfo, callback?: () => void) => {
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
@@ -49,7 +49,7 @@
   const navigateToUserProfileBottomSheet =
     useNavigateToUserProfileBottomSheet();
 
-  const [selectedUserID, setSelectedUserID] = React.useState();
+  const [selectedUserID, setSelectedUserID] = React.useState<?string>();
 
   // This useEffect will call navigateToUserProfileBottomSheet whenever the
   //  MessageReactionsModal is unmounting and there is a selectedUserID.
diff --git a/native/chat/message-results-screen.react.js b/native/chat/message-results-screen.react.js
--- a/native/chat/message-results-screen.react.js
+++ b/native/chat/message-results-screen.react.js
@@ -11,6 +11,7 @@
   createMessageInfo,
   isInvalidPinSourceForThread,
 } from 'lib/shared/message-utils.js';
+import type { RawMessageInfo } from 'lib/types/message-types.js';
 import type { MinimallyEncodedThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
 
@@ -22,6 +23,7 @@
 import { useSelector } from '../redux/redux-utils.js';
 import { useStyles } from '../themes/colors.js';
 import type { ChatMessageItemWithHeight } from '../types/chat-types.js';
+import type { VerticalBounds } from '../types/layout-types.js';
 
 export type MessageResultsScreenParams = {
   +threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
@@ -37,13 +39,18 @@
   const { threadInfo } = route.params;
   const styles = useStyles(unboundStyles);
   const { id: threadID } = threadInfo;
-  const [rawMessageResults, setRawMessageResults] = React.useState([]);
+  const [rawMessageResults, setRawMessageResults] = React.useState<
+    $ReadOnlyArray<RawMessageInfo>,
+  >([]);
 
   const measureMessages = useHeightMeasurer();
-  const [measuredMessages, setMeasuredMessages] = React.useState([]);
+  const [measuredMessages, setMeasuredMessages] = React.useState<
+    $ReadOnlyArray<ChatMessageItemWithHeight>,
+  >([]);
 
-  const [messageVerticalBounds, setMessageVerticalBounds] = React.useState();
-  const scrollViewContainerRef = React.useRef();
+  const [messageVerticalBounds, setMessageVerticalBounds] =
+    React.useState<?VerticalBounds>();
+  const scrollViewContainerRef = React.useRef<?React.ElementRef<typeof View>>();
 
   const callFetchPinnedMessages = useFetchPinnedMessages();
   const userInfos = useSelector(state => state.userStore.userInfos);
diff --git a/native/chat/settings/add-users-modal.react.js b/native/chat/settings/add-users-modal.react.js
--- a/native/chat/settings/add-users-modal.react.js
+++ b/native/chat/settings/add-users-modal.react.js
@@ -23,7 +23,10 @@
 
 import Button from '../../components/button.react.js';
 import Modal from '../../components/modal.react.js';
-import { createTagInput } from '../../components/tag-input.react.js';
+import {
+  createTagInput,
+  type BaseTagInput,
+} from '../../components/tag-input.react.js';
 import UserList from '../../components/user-list.react.js';
 import type { RootNavigationProp } from '../../navigation/root-navigator.react.js';
 import type { NavigationRoute } from '../../navigation/route-names.js';
@@ -56,7 +59,7 @@
     $ReadOnlyArray<AccountUserInfo>,
   >([]);
 
-  const tagInputRef = React.useRef();
+  const tagInputRef = React.useRef<?BaseTagInput<AccountUserInfo>>();
   const onUnknownErrorAlertAcknowledged = React.useCallback(() => {
     setUsernameInputText('');
     setUserInfoInputArray([]);
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
@@ -48,7 +48,7 @@
   const galleryItemWidth =
     (width - 32 - (numColumns - 1) * galleryItemGap) / numColumns;
   const { threadID, limit, verticalBounds, offset, activeTab } = props;
-  const [mediaInfos, setMediaInfos] = React.useState([]);
+  const [mediaInfos, setMediaInfos] = React.useState<$ReadOnlyArray<Media>>([]);
   const callFetchThreadMedia = useFetchThreadMedia();
 
   React.useEffect(() => {
@@ -153,7 +153,7 @@
 function MediaGalleryItem(props: MediaGalleryItemProps): React.Node {
   const navigation = useNavigation();
   const route = useRoute();
-  const ref = React.useRef(null);
+  const ref = React.useRef<?React.ElementRef<typeof View>>(null);
   const onLayout = React.useCallback(() => {}, []);
   const { threadID, verticalBounds, memoizedStyles, item, index } = props;
 
diff --git a/native/chat/swipeable-thread.react.js b/native/chat/swipeable-thread.react.js
--- a/native/chat/swipeable-thread.react.js
+++ b/native/chat/swipeable-thread.react.js
@@ -3,6 +3,8 @@
 import MaterialIcon from '@expo/vector-icons/MaterialCommunityIcons.js';
 import { useNavigation } from '@react-navigation/native';
 import * as React from 'react';
+// eslint-disable-next-line import/extensions
+import SwipeableComponent from 'react-native-gesture-handler/Swipeable';
 
 import useToggleUnreadStatus from 'lib/hooks/toggle-unread-status.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
@@ -18,7 +20,7 @@
   +children: React.Node,
 };
 function SwipeableThread(props: Props): React.Node {
-  const swipeable = React.useRef();
+  const swipeable = React.useRef<?SwipeableComponent>();
   const navigation = useNavigation();
   React.useEffect(() => {
     return navigation.addListener('blur', () => {
diff --git a/native/chat/thread-list-modal.react.js b/native/chat/thread-list-modal.react.js
--- a/native/chat/thread-list-modal.react.js
+++ b/native/chat/thread-list-modal.react.js
@@ -61,7 +61,8 @@
     modalTitle,
   } = props;
 
-  const searchTextInputRef = React.useRef();
+  const searchTextInputRef =
+    React.useRef<?React.ElementRef<typeof TextInput>>();
   const setSearchTextInputRef = React.useCallback(
     async (textInput: ?React.ElementRef<typeof TextInput>) => {
       searchTextInputRef.current = textInput;
diff --git a/native/community-creation/community-creation-members.react.js b/native/community-creation/community-creation-members.react.js
--- a/native/community-creation/community-creation-members.react.js
+++ b/native/community-creation/community-creation-members.react.js
@@ -25,7 +25,10 @@
 import RegistrationContainer from '../account/registration/registration-container.react.js';
 import { useNavigateToThread } from '../chat/message-list-types.js';
 import LinkButton from '../components/link-button.react.js';
-import { createTagInput } from '../components/tag-input.react.js';
+import {
+  createTagInput,
+  type BaseTagInput,
+} from '../components/tag-input.react.js';
 import UserList from '../components/user-list.react.js';
 import type { NavigationRoute } from '../navigation/route-names.js';
 import { useSelector } from '../redux/redux-utils.js';
@@ -175,7 +178,7 @@
     [otherUserInfos, selectedUserIDs],
   );
 
-  const tagInputRef = React.useRef();
+  const tagInputRef = React.useRef<?BaseTagInput<AccountUserInfo>>();
 
   return (
     <RegistrationContainer>
diff --git a/native/components/gesture-touchable-opacity.react.js b/native/components/gesture-touchable-opacity.react.js
--- a/native/components/gesture-touchable-opacity.react.js
+++ b/native/components/gesture-touchable-opacity.react.js
@@ -116,12 +116,12 @@
 
   const curOpacity = useValue(1);
 
-  const pressClockRef = React.useRef();
+  const pressClockRef = React.useRef<?Clock>();
   if (!pressClockRef.current) {
     pressClockRef.current = new Clock();
   }
   const pressClock = pressClockRef.current;
-  const resetClockRef = React.useRef();
+  const resetClockRef = React.useRef<?Clock>();
   if (!resetClockRef.current) {
     resetClockRef.current = new Clock();
   }
diff --git a/native/components/search.react.js b/native/components/search.react.js
--- a/native/components/search.react.js
+++ b/native/components/search.react.js
@@ -39,7 +39,7 @@
   const loggedIn = useSelector(isLoggedIn);
   const styles = useStyles(unboundStyles);
   const colors = useColors();
-  const prevLoggedInRef = React.useRef();
+  const prevLoggedInRef = React.useRef<?boolean>();
   React.useEffect(() => {
     const prevLoggedIn = prevLoggedInRef.current;
     prevLoggedInRef.current = loggedIn;
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
@@ -41,6 +41,11 @@
   NativeMethods,
 >;
 
+type VideoRef = {
+  +seek: number => mixed,
+  ...
+};
+
 /* eslint-disable import/no-named-as-default-member */
 const {
   Extrapolate,
@@ -175,7 +180,7 @@
   const footerY = useValue(-1);
   const footerWidth = useValue(-1);
   const footerHeight = useValue(-1);
-  const footerRef = React.useRef();
+  const footerRef = React.useRef<?React.ElementRef<typeof View>>();
   const footer = footerRef.current;
   const onFooterLayoutCalledRef = React.useRef(false);
   const onFooterLayout = React.useCallback(() => {
@@ -571,7 +576,7 @@
   const [spinnerVisible, setSpinnerVisible] = useState(true);
   const [timeElapsed, setTimeElapsed] = useState('0:00');
   const [totalDuration, setTotalDuration] = useState('0:00');
-  const videoRef = React.useRef();
+  const videoRef = React.useRef<?VideoRef>();
 
   const backgroundedOrInactive = useIsAppBackgroundedOrInactive();
   React.useEffect(() => {
diff --git a/native/navigation/deep-links-context-provider.react.js b/native/navigation/deep-links-context-provider.react.js
--- a/native/navigation/deep-links-context-provider.react.js
+++ b/native/navigation/deep-links-context-provider.react.js
@@ -44,7 +44,7 @@
 };
 function DeepLinksContextProvider(props: Props): React.Node {
   const { children } = props;
-  const [currentLink, setCurrentLink] = React.useState(null);
+  const [currentLink, setCurrentLink] = React.useState<?string>(null);
 
   React.useEffect(() => {
     // This listener listens for an event where a user clicked a link when the
diff --git a/native/navigation/disconnected-bar.react.js b/native/navigation/disconnected-bar.react.js
--- a/native/navigation/disconnected-bar.react.js
+++ b/native/navigation/disconnected-bar.react.js
@@ -25,7 +25,7 @@
 };
 function DisconnectedBar(props: Props): React.Node {
   const { shouldShowDisconnectedBar } = useShouldShowDisconnectedBar();
-  const showingRef = React.useRef();
+  const showingRef = React.useRef<?Animated.Value>();
   if (!showingRef.current) {
     showingRef.current = new Animated.Value(shouldShowDisconnectedBar ? 1 : 0);
   }
diff --git a/native/navigation/navigation-handler.react.js b/native/navigation/navigation-handler.react.js
--- a/native/navigation/navigation-handler.react.js
+++ b/native/navigation/navigation-handler.react.js
@@ -66,7 +66,7 @@
 
   const loggedIn = hasCurrentUserInfo && hasUserCookie;
   const navLoggedIn = useIsAppLoggedIn();
-  const prevLoggedInRef = React.useRef();
+  const prevLoggedInRef = React.useRef<?boolean>();
 
   React.useEffect(() => {
     if (loggedIn === prevLoggedInRef.current) {
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
@@ -8,7 +8,10 @@
 
 import { useDispatch } from 'lib/utils/redux-utils.js';
 
-import { KeyboardContext } from '../keyboard/keyboard-state.js';
+import {
+  KeyboardContext,
+  type KeyboardState,
+} from '../keyboard/keyboard-state.js';
 import { updateDimensionsActiveType } from '../redux/action-types.js';
 import { useSelector } from '../redux/redux-utils.js';
 import type { LayoutEvent } from '../types/react-native.js';
@@ -21,7 +24,7 @@
 
 type Props = React.ElementConfig<typeof BottomTabBar>;
 function TabBar(props: Props) {
-  const tabBarVisibleRef = React.useRef();
+  const tabBarVisibleRef = React.useRef<?Value>();
   if (!tabBarVisibleRef.current) {
     tabBarVisibleRef.current = new Value(1);
   }
@@ -30,7 +33,7 @@
   const keyboardState = React.useContext(KeyboardContext);
   const shouldHideTabBar = keyboardState?.mediaGalleryOpen;
 
-  const prevKeyboardStateRef = React.useRef();
+  const prevKeyboardStateRef = React.useRef<?KeyboardState>();
   React.useEffect(() => {
     prevKeyboardStateRef.current = keyboardState;
   }, [keyboardState]);
@@ -54,7 +57,7 @@
     [keyboardState, prevKeyboardState, tabBarVisible],
   );
 
-  const prevShouldHideTabBarRef = React.useRef(false);
+  const prevShouldHideTabBarRef = React.useRef<?boolean>(false);
   React.useEffect(() => {
     const prevShouldHideTabBar = prevShouldHideTabBarRef.current;
     if (shouldHideTabBar && !prevShouldHideTabBar) {
diff --git a/native/profile/keyserver-selection-bottom-sheet.react.js b/native/profile/keyserver-selection-bottom-sheet.react.js
--- a/native/profile/keyserver-selection-bottom-sheet.react.js
+++ b/native/profile/keyserver-selection-bottom-sheet.react.js
@@ -48,7 +48,8 @@
   invariant(bottomSheetContext, 'bottomSheetContext should be set');
   const { setContentHeight } = bottomSheetContext;
 
-  const removeKeyserverContainerRef = React.useRef();
+  const removeKeyserverContainerRef =
+    React.useRef<?React.ElementRef<typeof View>>();
   const bottomSheetRef = React.useRef();
 
   const colors = useColors();
diff --git a/native/profile/secondary-device-qr-code-scanner.react.js b/native/profile/secondary-device-qr-code-scanner.react.js
--- a/native/profile/secondary-device-qr-code-scanner.react.js
+++ b/native/profile/secondary-device-qr-code-scanner.react.js
@@ -20,7 +20,7 @@
 };
 // eslint-disable-next-line no-unused-vars
 function SecondaryDeviceQRCodeScanner(props: Props): React.Node {
-  const [hasPermission, setHasPermission] = React.useState(null);
+  const [hasPermission, setHasPermission] = React.useState<?boolean>(null);
   const [scanned, setScanned] = React.useState(false);
 
   const styles = useStyles(unboundStyles);
diff --git a/native/redux/dimensions-updater.react.js b/native/redux/dimensions-updater.react.js
--- a/native/redux/dimensions-updater.react.js
+++ b/native/redux/dimensions-updater.react.js
@@ -62,7 +62,7 @@
   const frame = useSafeAreaFrame();
   const insets = useSafeAreaInsets();
 
-  const keyboardShowingRef = React.useRef();
+  const keyboardShowingRef = React.useRef<?boolean>();
   const keyboardShow = React.useCallback(() => {
     keyboardShowingRef.current = true;
   }, []);
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -6,6 +6,7 @@
 import type {
   PossiblyStaleNavigationState,
   UnsafeContainerActionEvent,
+  GenericNavigationAction,
 } from '@react-navigation/core';
 import { useReduxDevToolsExtension } from '@react-navigation/devtools';
 import { NavigationContainer } from '@react-navigation/native';
@@ -50,7 +51,10 @@
 import { defaultNavigationState } from './navigation/default-state.js';
 import DisconnectedBarVisibilityHandler from './navigation/disconnected-bar-visibility-handler.react.js';
 import { setGlobalNavContext } from './navigation/icky-global.js';
-import { NavContext } from './navigation/navigation-context.js';
+import {
+  NavContext,
+  type NavContextType,
+} from './navigation/navigation-context.js';
 import NavigationHandler from './navigation/navigation-handler.react.js';
 import { validNavState } from './navigation/navigation-utils.js';
 import OrientationHandler from './navigation/orientation-handler.react.js';
@@ -86,15 +90,20 @@
 SplashScreen.preventAutoHideAsync().catch(console.log);
 
 function Root() {
-  const navStateRef = React.useRef();
-  const navDispatchRef = React.useRef();
+  const navStateRef = React.useRef<?PossiblyStaleNavigationState>();
+  const navDispatchRef =
+    React.useRef<?(
+      action:
+        | GenericNavigationAction
+        | (PossiblyStaleNavigationState => GenericNavigationAction),
+    ) => void>();
   const navStateInitializedRef = React.useRef(false);
 
   // We call this here to start the loading process
   // We gate the UI on the fonts loading in AppNavigator
   useLoadCommFonts();
 
-  const [navContext, setNavContext] = React.useState(null);
+  const [navContext, setNavContext] = React.useState<?NavContextType>(null);
   const updateNavContext = React.useCallback(() => {
     if (
       !navStateRef.current ||
@@ -165,7 +174,7 @@
   );
 
   const frozen = useSelector(state => state.frozen);
-  const queuedActionsRef = React.useRef([]);
+  const queuedActionsRef = React.useRef<Array<GenericNavigationAction>>([]);
   const onNavigationStateChange = React.useCallback(
     (state: ?PossiblyStaleNavigationState) => {
       invariant(state, 'nav state should be non-null');
@@ -200,7 +209,8 @@
     [updateNavContext, frozen],
   );
 
-  const navContainerRef = React.useRef();
+  const navContainerRef =
+    React.useRef<?React.ElementRef<typeof NavigationContainer>>();
   const containerRef = React.useCallback(
     (navContainer: ?React.ElementRef<typeof NavigationContainer>) => {
       navContainerRef.current = navContainer;
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
@@ -26,6 +26,7 @@
 import { useSelector } from '../redux/redux-utils.js';
 import { useStyles } from '../themes/colors.js';
 import type { ChatMessageItemWithHeight } from '../types/chat-types.js';
+import type { VerticalBounds } from '../types/layout-types.js';
 
 export type MessageSearchParams = {
   +threadInfo: ThreadInfo | MinimallyEncodedThreadInfo,
@@ -47,7 +48,9 @@
   }, [props.navigation, clearQuery]);
 
   const [lastID, setLastID] = React.useState();
-  const [searchResults, setSearchResults] = React.useState([]);
+  const [searchResults, setSearchResults] = React.useState<
+    $ReadOnlyArray<RawMessageInfo>,
+  >([]);
   const [endReached, setEndReached] = React.useState(false);
 
   const appendSearchResults = React.useCallback(
@@ -112,7 +115,9 @@
     return result;
   }, [chatMessageInfos, endReached, translatedSearchResults]);
 
-  const [measuredMessages, setMeasuredMessages] = React.useState([]);
+  const [measuredMessages, setMeasuredMessages] = React.useState<
+    $ReadOnlyArray<ChatMessageItemWithHeight>,
+  >([]);
 
   const measureMessages = useHeightMeasurer();
   const measureCallback = React.useCallback(
@@ -126,8 +131,9 @@
     measureMessages(filteredChatMessageInfos, threadInfo, measureCallback);
   }, [filteredChatMessageInfos, measureCallback, measureMessages, threadInfo]);
 
-  const [messageVerticalBounds, setMessageVerticalBounds] = React.useState();
-  const scrollViewContainerRef = React.useRef();
+  const [messageVerticalBounds, setMessageVerticalBounds] =
+    React.useState<?VerticalBounds>();
+  const scrollViewContainerRef = React.useRef<?React.ElementRef<typeof View>>();
 
   const onLayout = React.useCallback(() => {
     scrollViewContainerRef.current?.measure(
diff --git a/native/user-profile/user-profile-avatar.react.js b/native/user-profile/user-profile-avatar.react.js
--- a/native/user-profile/user-profile-avatar.react.js
+++ b/native/user-profile/user-profile-avatar.react.js
@@ -25,7 +25,7 @@
 
   const overlayContext = React.useContext(OverlayContext);
 
-  const avatarRef = React.useRef();
+  const avatarRef = React.useRef<?React.ElementRef<typeof View>>();
 
   const onPressAvatar = React.useCallback(() => {
     invariant(overlayContext, 'UserProfileAvatar should have OverlayContext');
diff --git a/native/user-profile/user-profile-menu-button.react.js b/native/user-profile/user-profile-menu-button.react.js
--- a/native/user-profile/user-profile-menu-button.react.js
+++ b/native/user-profile/user-profile-menu-button.react.js
@@ -42,7 +42,7 @@
 
   const overlayContext = React.useContext(OverlayContext);
 
-  const menuButtonRef = React.useRef();
+  const menuButtonRef = React.useRef<?React.ElementRef<typeof View>>();
 
   const visibleTooltipActionEntryIDs = React.useMemo(() => {
     const result = [];
diff --git a/native/utils/animation-utils.js b/native/utils/animation-utils.js
--- a/native/utils/animation-utils.js
+++ b/native/utils/animation-utils.js
@@ -184,7 +184,7 @@
 }
 
 function useReanimatedValueForBoolean(booleanValue: boolean): Value {
-  const reanimatedValueRef = React.useRef();
+  const reanimatedValueRef = React.useRef<?Value>();
   if (!reanimatedValueRef.current) {
     reanimatedValueRef.current = new Value(booleanValue ? 1 : 0);
   }