From e5bbfeb26ecd93e5be79ae9c96421fa53e6f87d4 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Sun, 26 Apr 2026 21:19:08 +0200
Subject: [PATCH] refactor: optimize EmailRow component and improve row press
handling in EmailListScreen
---
src/components/calendar/MonthView.tsx | 4 +-
src/components/calendar/WeekView.tsx | 4 +-
src/screens/CalendarScreen.tsx | 4 +-
src/screens/EmailListScreen.tsx | 68 +++++++++++++++++++--------
4 files changed, 56 insertions(+), 24 deletions(-)
diff --git a/src/components/calendar/MonthView.tsx b/src/components/calendar/MonthView.tsx
index 0fa9717f..2b3b3932 100644
--- a/src/components/calendar/MonthView.tsx
+++ b/src/components/calendar/MonthView.tsx
@@ -32,7 +32,7 @@ interface MonthViewProps {
onLongPressDate?: (date: Date) => void;
}
-export function MonthView({
+function MonthViewInner({
currentDate,
selectedDate,
events,
@@ -118,6 +118,8 @@ export function MonthView({
);
}
+export const MonthView = React.memo(MonthViewInner);
+
const styles = StyleSheet.create({
grid: { paddingHorizontal: spacing.sm },
weekdayRow: { flexDirection: 'row', paddingHorizontal: spacing.xs, marginBottom: 4 },
diff --git a/src/components/calendar/WeekView.tsx b/src/components/calendar/WeekView.tsx
index 8604c0ed..7fb8a680 100644
--- a/src/components/calendar/WeekView.tsx
+++ b/src/components/calendar/WeekView.tsx
@@ -39,7 +39,7 @@ interface WeekViewProps {
weekStartsOn?: 0 | 1;
}
-export function WeekView({
+function WeekViewInner({
selectedDate,
events,
calendars,
@@ -303,6 +303,8 @@ export function WeekView({
);
}
+export const WeekView = React.memo(WeekViewInner);
+
function minutesToTimeLabel(minutes: number): string {
const h = Math.floor(minutes / 60);
const m = minutes % 60;
diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx
index af2a363c..4a58d349 100644
--- a/src/screens/CalendarScreen.tsx
+++ b/src/screens/CalendarScreen.tsx
@@ -319,7 +319,7 @@ export default function CalendarScreen() {
eventsByDay={eventsByDay}
calendars={calendars}
onSelectDate={handleSelectDate}
- onLongPressDate={(date) => openCreate(date)}
+ onLongPressDate={openCreate}
/>
)}
{viewMode === 'week' && (
@@ -330,7 +330,7 @@ export default function CalendarScreen() {
calendars={calendars}
onSelectDate={handleSelectDate}
onSelectEvent={setDetailEvent}
- onCreateAtTime={(date) => openCreate(date)}
+ onCreateAtTime={openCreate}
/>
)}
{viewMode === 'agenda' && (
diff --git a/src/screens/EmailListScreen.tsx b/src/screens/EmailListScreen.tsx
index d6deb694..3d0acd0d 100644
--- a/src/screens/EmailListScreen.tsx
+++ b/src/screens/EmailListScreen.tsx
@@ -43,7 +43,7 @@ function formatRelativeTime(date: Date): string {
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
-function EmailRow({
+const EmailRow = React.memo(function EmailRow({
item,
onPress,
onLongPress,
@@ -51,8 +51,8 @@ function EmailRow({
selectionMode,
}: {
item: Email;
- onPress: () => void;
- onLongPress: () => void;
+ onPress: (id: string) => void;
+ onLongPress: (id: string) => void;
selected: boolean;
selectionMode: boolean;
}) {
@@ -61,6 +61,9 @@ function EmailRow({
const unread = isUnread(item);
const starred = isStarred(item);
+ const handlePress = React.useCallback(() => onPress(item.id), [onPress, item.id]);
+ const handleLongPress = React.useCallback(() => onLongPress(item.id), [onLongPress, item.id]);
+
return (
[
@@ -68,8 +71,8 @@ function EmailRow({
pressed && styles.emailRowPressed,
selected && styles.emailRowSelected,
]}
- onPress={onPress}
- onLongPress={onLongPress}
+ onPress={handlePress}
+ onLongPress={handleLongPress}
delayLongPress={300}
>
{selectionMode && (
@@ -117,7 +120,11 @@ function EmailRow({
);
-}
+});
+
+const EmailRowSeparator = () => ;
+
+const emailKeyExtractor = (item: Email) => item.id;
interface EmailListScreenProps {
onEmailPress?: (email: Email) => void;
@@ -152,6 +159,16 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
const allSelected =
emails.length > 0 && emails.every((e) => selectedIds.has(e.id));
+ // Refs so row press handlers stay referentially stable across renders.
+ // FlatList rows then skip re-render when the parent re-renders for unrelated
+ // reasons (e.g. opening the filter modal).
+ const onEmailPressRef = React.useRef(onEmailPress);
+ React.useEffect(() => { onEmailPressRef.current = onEmailPress; }, [onEmailPress]);
+ const selectionModeRef = React.useRef(selectionMode);
+ React.useEffect(() => { selectionModeRef.current = selectionMode; }, [selectionMode]);
+ const emailsRef = React.useRef(emails);
+ React.useEffect(() => { emailsRef.current = emails; }, [emails]);
+
const toggleSelect = React.useCallback((id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
@@ -161,6 +178,28 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
});
}, []);
+ const handleRowPress = React.useCallback((id: string) => {
+ if (selectionModeRef.current) {
+ toggleSelect(id);
+ } else {
+ const email = emailsRef.current.find((e) => e.id === id);
+ if (email) onEmailPressRef.current?.(email);
+ }
+ }, [toggleSelect]);
+
+ const renderEmailRow = React.useCallback(
+ ({ item }: { item: Email }) => (
+
+ ),
+ [selectedIds, selectionMode, handleRowPress, toggleSelect],
+ );
+
const clearSelection = React.useCallback(() => {
setSelectedIds(new Set());
}, []);
@@ -488,20 +527,9 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
) : (
item.id}
- renderItem={({ item }) => (
- {
- if (selectionMode) toggleSelect(item.id);
- else onEmailPress?.(item);
- }}
- onLongPress={() => toggleSelect(item.id)}
- />
- )}
- ItemSeparatorComponent={() => }
+ keyExtractor={emailKeyExtractor}
+ renderItem={renderEmailRow}
+ ItemSeparatorComponent={EmailRowSeparator}
contentContainerStyle={styles.listContent}
onEndReached={() => { void loadMoreEmails(); }}
onEndReachedThreshold={0.3}