Skip to content

Commit d27f13a

Browse files
committed
refactor(demo): replace LoadingOverlay with inline loading states
1 parent 44c7895 commit d27f13a

8 files changed

Lines changed: 86 additions & 54 deletions

File tree

examples/demo/src/components/ListWidgets.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState } from 'react';
2-
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
2+
import { ActivityIndicator, View, Text, TouchableOpacity, StyleSheet } from 'react-native';
33
import Icon from 'react-native-vector-icons/MaterialIcons';
44

55
import { AppColors, AppTextStyles, AppTheme } from '../theme';
@@ -111,6 +111,19 @@ export function EmptyState({ message, testID }: EmptyStateProps) {
111111
);
112112
}
113113

114+
// LoadingState - shown inside a list area while data is being fetched
115+
interface LoadingStateProps {
116+
testID?: string;
117+
}
118+
119+
export function LoadingState({ testID }: LoadingStateProps) {
120+
return (
121+
<View style={styles.emptyContainer} testID={testID}>
122+
<ActivityIndicator size="small" color={AppColors.osPrimary} />
123+
</View>
124+
);
125+
}
126+
114127
// PairList (simple, no collapse)
115128
interface PairListProps {
116129
items: [string, string][];
@@ -158,13 +171,15 @@ interface CollapsibleSingleListProps {
158171
items: string[];
159172
onDelete?: (value: string) => void;
160173
emptyMessage: string;
174+
loading?: boolean;
161175
sectionKey?: string;
162176
}
163177

164178
export function CollapsibleSingleList({
165179
items,
166180
onDelete,
167181
emptyMessage,
182+
loading = false,
168183
sectionKey,
169184
}: CollapsibleSingleListProps) {
170185
const [expanded, setExpanded] = useState(false);
@@ -175,7 +190,14 @@ export function CollapsibleSingleList({
175190
if (items.length === 0) {
176191
return (
177192
<View style={AppTheme.card}>
178-
<EmptyState message={emptyMessage} />
193+
{loading ? (
194+
<LoadingState testID={sectionKey ? `${sectionKey}_loading` : undefined} />
195+
) : (
196+
<EmptyState
197+
message={emptyMessage}
198+
testID={sectionKey ? `${sectionKey}_empty` : undefined}
199+
/>
200+
)}
179201
</View>
180202
);
181203
}

examples/demo/src/components/LoadingOverlay.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.

examples/demo/src/components/sections/AliasesSection.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { View, StyleSheet } from 'react-native';
33

44
import { AppTheme, AppSpacing } from '../../theme';
55
import ActionButton from '../ActionButton';
6-
import { PairList, EmptyState } from '../ListWidgets';
6+
import { PairList, EmptyState, LoadingState } from '../ListWidgets';
77
import MultiPairInputModal from '../modals/MultiPairInputModal';
88
import PairInputModal from '../modals/PairInputModal';
99
import SectionCard from '../SectionCard';
@@ -12,12 +12,19 @@ const FILTERED_KEYS = ['external_id', 'onesignal_id'];
1212

1313
interface Props {
1414
aliases: [string, string][];
15+
loading?: boolean;
1516
onAdd: (label: string, id: string) => void;
1617
onAddMultiple: (pairs: Record<string, string>) => void;
1718
onInfoTap?: () => void;
1819
}
1920

20-
export default function AliasesSection({ aliases, onAdd, onAddMultiple, onInfoTap }: Props) {
21+
export default function AliasesSection({
22+
aliases,
23+
loading = false,
24+
onAdd,
25+
onAddMultiple,
26+
onInfoTap,
27+
}: Props) {
2128
const [addVisible, setAddVisible] = useState(false);
2229
const [addMultipleVisible, setAddMultipleVisible] = useState(false);
2330

@@ -27,7 +34,11 @@ export default function AliasesSection({ aliases, onAdd, onAddMultiple, onInfoTa
2734
<SectionCard title="Aliases" onInfoTap={onInfoTap} sectionKey="aliases">
2835
{filtered.length === 0 ? (
2936
<View style={[AppTheme.card, styles.listCard]}>
30-
<EmptyState message="No aliases added" testID="aliases_empty" />
37+
{loading ? (
38+
<LoadingState testID="aliases_loading" />
39+
) : (
40+
<EmptyState message="No aliases added" testID="aliases_empty" />
41+
)}
3142
</View>
3243
) : (
3344
<View style={styles.listCard}>

examples/demo/src/components/sections/EmailsSection.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,19 @@ import SectionCard from '../SectionCard';
99

1010
interface Props {
1111
emails: string[];
12+
loading?: boolean;
1213
onAdd: (email: string) => void;
1314
onRemove: (email: string) => void;
1415
onInfoTap?: () => void;
1516
}
1617

17-
export default function EmailsSection({ emails, onAdd, onRemove, onInfoTap }: Props) {
18+
export default function EmailsSection({
19+
emails,
20+
loading = false,
21+
onAdd,
22+
onRemove,
23+
onInfoTap,
24+
}: Props) {
1825
const [addVisible, setAddVisible] = useState(false);
1926

2027
return (
@@ -24,6 +31,7 @@ export default function EmailsSection({ emails, onAdd, onRemove, onInfoTap }: Pr
2431
items={emails}
2532
onDelete={onRemove}
2633
emptyMessage="No emails added"
34+
loading={loading}
2735
sectionKey="emails"
2836
/>
2937
</View>

examples/demo/src/components/sections/SmsSection.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,19 @@ import SectionCard from '../SectionCard';
99

1010
interface Props {
1111
smsNumbers: string[];
12+
loading?: boolean;
1213
onAdd: (sms: string) => void;
1314
onRemove: (sms: string) => void;
1415
onInfoTap?: () => void;
1516
}
1617

17-
export default function SmsSection({ smsNumbers, onAdd, onRemove, onInfoTap }: Props) {
18+
export default function SmsSection({
19+
smsNumbers,
20+
loading = false,
21+
onAdd,
22+
onRemove,
23+
onInfoTap,
24+
}: Props) {
1825
const [addVisible, setAddVisible] = useState(false);
1926

2027
return (
@@ -24,6 +31,7 @@ export default function SmsSection({ smsNumbers, onAdd, onRemove, onInfoTap }: P
2431
items={smsNumbers}
2532
onDelete={onRemove}
2633
emptyMessage="No SMS added"
34+
loading={loading}
2735
sectionKey="sms"
2836
/>
2937
</View>

examples/demo/src/components/sections/TagsSection.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { View, StyleSheet } from 'react-native';
33

44
import { AppTheme, AppSpacing } from '../../theme';
55
import ActionButton from '../ActionButton';
6-
import { PairList, EmptyState } from '../ListWidgets';
6+
import { PairList, EmptyState, LoadingState } from '../ListWidgets';
77
import MultiPairInputModal from '../modals/MultiPairInputModal';
88
import MultiSelectRemoveModal from '../modals/MultiSelectRemoveModal';
99
import PairInputModal from '../modals/PairInputModal';
1010
import SectionCard from '../SectionCard';
1111

1212
interface Props {
1313
tags: [string, string][];
14+
loading?: boolean;
1415
onAdd: (key: string, value: string) => void;
1516
onAddMultiple: (pairs: Record<string, string>) => void;
1617
onRemoveSelected: (keys: string[]) => void;
@@ -19,6 +20,7 @@ interface Props {
1920

2021
export default function TagsSection({
2122
tags,
23+
loading = false,
2224
onAdd,
2325
onAddMultiple,
2426
onRemoveSelected,
@@ -32,7 +34,11 @@ export default function TagsSection({
3234
<SectionCard title="Tags" onInfoTap={onInfoTap} sectionKey="tags">
3335
{tags.length === 0 ? (
3436
<View style={[AppTheme.card, styles.listCard]}>
35-
<EmptyState message="No tags added" testID="tags_empty" />
37+
{loading ? (
38+
<LoadingState testID="tags_loading" />
39+
) : (
40+
<EmptyState message="No tags added" testID="tags_empty" />
41+
)}
3642
</View>
3743
) : (
3844
<View style={styles.listCard}>

examples/demo/src/hooks/useOneSignal.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,10 @@ function useOneSignalState(): UseOneSignalReturn {
117117
requestSequenceRef.current = requestId;
118118

119119
const onesignalId = await repository.getOnesignalId();
120-
if (!onesignalId) {
121-
if (mountedRef.current && requestSequenceRef.current === requestId) {
122-
setIsLoading(false);
123-
}
124-
return;
125-
}
120+
if (!onesignalId) return;
126121

127122
const userData = await repository.fetchUser(onesignalId);
128-
if (!userData) {
129-
if (mountedRef.current && requestSequenceRef.current === requestId) {
130-
setIsLoading(false);
131-
}
132-
return;
133-
}
123+
if (!userData) return;
134124

135125
const externalId = await repository.getExternalId();
136126
if (!mountedRef.current || requestSequenceRef.current !== requestId) {
@@ -142,7 +132,6 @@ function useOneSignalState(): UseOneSignalReturn {
142132
setEmailsList(userData.emails);
143133
setSmsNumbersList(userData.smsNumbers);
144134
setExternalUserId(externalId ?? userData.externalId);
145-
setIsLoading(false);
146135
}, []);
147136

148137
useEffect(() => {
@@ -212,7 +201,15 @@ function useOneSignalState(): UseOneSignalReturn {
212201
return;
213202
}
214203
setIsLoading(true);
215-
await fetchUserDataFromApi();
204+
try {
205+
await fetchUserDataFromApi();
206+
} catch (err) {
207+
console.error(`fetchUserDataFromApi error: ${String(err)}`);
208+
} finally {
209+
if (mountedRef.current) {
210+
setIsLoading(false);
211+
}
212+
}
216213
};
217214

218215
const load = async () => {
@@ -298,7 +295,13 @@ function useOneSignalState(): UseOneSignalReturn {
298295

299296
if (onesignalId) {
300297
setIsLoading(true);
301-
await fetchUserDataFromApi();
298+
try {
299+
await fetchUserDataFromApi();
300+
} finally {
301+
if (mountedRef.current && !cancelled) {
302+
setIsLoading(false);
303+
}
304+
}
302305
}
303306
};
304307

examples/demo/src/screens/HomeScreen.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import React, { useEffect, useState } from 'react';
33
import { Platform, ScrollView, StyleSheet, View } from 'react-native';
44

55
import ActionButton from '../components/ActionButton';
6-
import LoadingOverlay from '../components/LoadingOverlay';
76
import TooltipModal from '../components/modals/TooltipModal';
87
import AliasesSection from '../components/sections/AliasesSection';
98
import AppSection from '../components/sections/AppSection';
@@ -100,27 +99,31 @@ export default function HomeScreen() {
10099

101100
<AliasesSection
102101
aliases={os.aliasesList}
102+
loading={os.isLoading}
103103
onAdd={os.addAlias}
104104
onAddMultiple={os.addAliases}
105105
onInfoTap={() => showTooltipModal('aliases')}
106106
/>
107107

108108
<EmailsSection
109109
emails={os.emailsList}
110+
loading={os.isLoading}
110111
onAdd={os.addEmail}
111112
onRemove={os.removeEmail}
112113
onInfoTap={() => showTooltipModal('emails')}
113114
/>
114115

115116
<SmsSection
116117
smsNumbers={os.smsNumbersList}
118+
loading={os.isLoading}
117119
onAdd={os.addSms}
118120
onRemove={os.removeSms}
119121
onInfoTap={() => showTooltipModal('sms')}
120122
/>
121123

122124
<TagsSection
123125
tags={os.tagsList}
126+
loading={os.isLoading}
124127
onAdd={os.addTag}
125128
onAddMultiple={os.addTags}
126129
onRemoveSelected={os.removeSelectedTags}
@@ -178,8 +181,6 @@ export default function HomeScreen() {
178181
<View style={styles.bottomSpacer} />
179182
</ScrollView>
180183

181-
<LoadingOverlay visible={os.isLoading} />
182-
183184
<TooltipModal
184185
visible={tooltipVisible}
185186
tooltip={activeTooltip}

0 commit comments

Comments
 (0)