fix: stop re-rendering hidden tabs and the mail list on every mailbox fetch

The tab navigator subscribed to the whole `mailboxes` array to show one
badge. Every mailbox fetch sets a new array, so each refresh re-rendered
the navigator and, through its inline render callback, the mail list,
also while a message was open on top of it. It now selects the inbox's
unread count, a number that only changes when the badge does.

Both navigators also set `freezeOnBlur`, so tabs out of view stop
re-rendering until they are shown again, and so do stack screens more
than one level below the top. On Fabric, native-stack deliberately
keeps the screen directly below the top live so the back transition
can show it, so the list under an open message still re-renders
through its own store subscriptions.
This commit is contained in:
Linus Rath
2026-09-24 19:35:47 +02:00
parent f2dd3a8b4b
commit 4a5d837cd7
+10 -3
View File
@@ -129,9 +129,12 @@ function LoadingScreen({ message }: { message: string }) {
function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParamList, 'MainTabs'>) {
const c = useColors();
const mailboxes = useEmailStore((state) => state.mailboxes);
// Select the count, not the list: every mailbox fetch sets a new array, and
// re-rendering here re-renders the mail list with it.
const inboxUnreadCount = useEmailStore(
(state) => state.mailboxes.find((mailbox) => mailbox.role === 'inbox')?.unreadEmails ?? 0,
);
const logout = useAuthStore((state) => state.logout);
const inboxUnreadCount = mailboxes.find((mailbox) => mailbox.role === 'inbox')?.unreadEmails ?? 0;
const hasCalendar = useHasCalendar();
const hasContacts = useHasContacts();
const hasFiles = useHasFiles();
@@ -146,6 +149,8 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
<Tab.Navigator
screenOptions={{
headerShown: false,
// Tabs out of view stop re-rendering until they are shown again.
freezeOnBlur: true,
tabBarActiveTintColor: c.text,
tabBarInactiveTintColor: c.textSecondary,
tabBarStyle: {
@@ -651,7 +656,9 @@ export default function App() {
return (
<NavigationContainer ref={navigationRef} theme={navigationTheme}>
<StatusBar style={statusBarStyle} />
<Stack.Navigator screenOptions={{ headerShown: false }}>
{/* On Fabric, native-stack keeps the screen right below the top live and
freezes the ones further down. */}
<Stack.Navigator screenOptions={{ headerShown: false, freezeOnBlur: true }}>
<Stack.Screen name="MainTabs" component={MainTabsNavigator} />
<Stack.Screen name="EmailThread" component={EmailThreadScreen} />
<Stack.Screen name="EmailSource" component={EmailSourceScreen} />