diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js
--- a/lib/shared/thread-utils.js
+++ b/lib/shared/thread-utils.js
@@ -10,7 +10,11 @@
   fetchMostRecentMessagesActionTypes,
   fetchMostRecentMessages,
 } from '../actions/message-actions';
-import { newThreadActionTypes } from '../actions/thread-actions';
+import {
+  changeThreadMemberRolesActionTypes,
+  newThreadActionTypes,
+  removeUsersFromThreadActionTypes,
+} from '../actions/thread-actions';
 import { searchUsers as searchUserCall } from '../actions/user-actions';
 import ashoat from '../facts/ashoat';
 import genesis from '../facts/genesis';
@@ -52,6 +56,7 @@
   type ThreadType,
   type ClientNewThreadRequest,
   type NewThreadResult,
+  type ChangeThreadSettingsPayload,
   threadTypes,
   threadPermissions,
   threadTypeIsCommunityRoot,
@@ -1266,6 +1271,55 @@
   return { threadSearchResults, usersSearchResults };
 }
 
+function removeMemberFromThread(
+  threadInfo: ThreadInfo,
+  memberInfo: RelativeMemberInfo,
+  dispatchActionPromise: DispatchActionPromise,
+  removeUserFromThreadServerCall: (
+    threadID: string,
+    memberIDs: $ReadOnlyArray<string>,
+  ) => Promise<ChangeThreadSettingsPayload>,
+) {
+  const customKeyName = `${removeUsersFromThreadActionTypes.started}:${memberInfo.id}`;
+  dispatchActionPromise(
+    removeUsersFromThreadActionTypes,
+    removeUserFromThreadServerCall(threadInfo.id, [memberInfo.id]),
+    { customKeyName },
+  );
+}
+
+function switchMemberAdminRoleInThread(
+  threadInfo: ThreadInfo,
+  memberInfo: RelativeMemberInfo,
+  isCurrentlyAdmin: boolean,
+  dispatchActionPromise: DispatchActionPromise,
+  changeUserRoleServerCall: (
+    threadID: string,
+    memberIDs: $ReadOnlyArray<string>,
+    newRole: string,
+  ) => Promise<ChangeThreadSettingsPayload>,
+) {
+  let newRole = null;
+  for (const roleID in threadInfo.roles) {
+    const role = threadInfo.roles[roleID];
+    if (isCurrentlyAdmin && role.isDefault) {
+      newRole = role.id;
+      break;
+    } else if (!isCurrentlyAdmin && roleIsAdminRole(role)) {
+      newRole = role.id;
+      break;
+    }
+  }
+  invariant(newRole !== null, 'Could not find new role');
+
+  const customKeyName = `${changeThreadMemberRolesActionTypes.started}:${memberInfo.id}`;
+  dispatchActionPromise(
+    changeThreadMemberRolesActionTypes,
+    changeUserRoleServerCall(threadInfo.id, [memberInfo.id], newRole),
+    { customKeyName },
+  );
+}
+
 export {
   colorIsDark,
   generateRandomColor,
@@ -1323,4 +1377,6 @@
   getCommunity,
   getThreadListSearchResults,
   useThreadListSearch,
+  removeMemberFromThread,
+  switchMemberAdminRoleInThread,
 };
diff --git a/native/chat/settings/thread-settings-member-tooltip-modal.react.js b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
--- a/native/chat/settings/thread-settings-member-tooltip-modal.react.js
+++ b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
@@ -1,16 +1,17 @@
 // @flow
 
-import invariant from 'invariant';
 import * as React from 'react';
 import { Alert } from 'react-native';
 
 import {
-  removeUsersFromThreadActionTypes,
   removeUsersFromThread,
-  changeThreadMemberRolesActionTypes,
   changeThreadMemberRoles,
 } from 'lib/actions/thread-actions';
-import { memberIsAdmin, roleIsAdminRole } from 'lib/shared/thread-utils';
+import {
+  memberIsAdmin,
+  removeMemberFromThread,
+  switchMemberAdminRoleInThread,
+} from 'lib/shared/thread-utils';
 import { stringForUser } from 'lib/shared/user-utils';
 import type { ThreadInfo, RelativeMemberInfo } from 'lib/types/thread-types';
 import type { DispatchFunctions, ActionFunc } from 'lib/utils/action-utils';
@@ -35,14 +36,13 @@
 ) {
   const { memberInfo, threadInfo } = route.params;
   const boundRemoveUsersFromThread = bindServerCall(removeUsersFromThread);
-  const onConfirmRemoveUser = () => {
-    const customKeyName = `${removeUsersFromThreadActionTypes.started}:${memberInfo.id}`;
-    dispatchFunctions.dispatchActionPromise(
-      removeUsersFromThreadActionTypes,
-      boundRemoveUsersFromThread(threadInfo.id, [memberInfo.id]),
-      { customKeyName },
+  const onConfirmRemoveUser = () =>
+    removeMemberFromThread(
+      threadInfo,
+      memberInfo,
+      dispatchFunctions.dispatchActionPromise,
+      boundRemoveUsersFromThread,
     );
-  };
 
   const userText = stringForUser(memberInfo);
   Alert.alert(
@@ -64,27 +64,14 @@
   const { memberInfo, threadInfo } = route.params;
   const isCurrentlyAdmin = memberIsAdmin(memberInfo, threadInfo);
   const boundChangeThreadMemberRoles = bindServerCall(changeThreadMemberRoles);
-  const onConfirmMakeAdmin = () => {
-    let newRole = null;
-    for (const roleID in threadInfo.roles) {
-      const role = threadInfo.roles[roleID];
-      if (isCurrentlyAdmin && role.isDefault) {
-        newRole = role.id;
-        break;
-      } else if (!isCurrentlyAdmin && roleIsAdminRole(role)) {
-        newRole = role.id;
-        break;
-      }
-    }
-    invariant(newRole !== null, 'Could not find new role');
-
-    const customKeyName = `${changeThreadMemberRolesActionTypes.started}:${memberInfo.id}`;
-    dispatchFunctions.dispatchActionPromise(
-      changeThreadMemberRolesActionTypes,
-      boundChangeThreadMemberRoles(threadInfo.id, [memberInfo.id], newRole),
-      { customKeyName },
+  const onConfirmMakeAdmin = () =>
+    switchMemberAdminRoleInThread(
+      threadInfo,
+      memberInfo,
+      isCurrentlyAdmin,
+      dispatchFunctions.dispatchActionPromise,
+      boundChangeThreadMemberRoles,
     );
-  };
 
   const userText = stringForUser(memberInfo);
   const actionClause = isCurrentlyAdmin