BTDeviceList
A high-performance list component for displaying available Bluetooth medical devices with real-time status updates and interactive selection capabilities.
Overview
The BTDeviceList component provides a comprehensive solution for displaying medical device information in a scrollable, optimized list format. It leverages FlashList for superior performance and integrates seamlessly with the device list context for state management.
Props Interface
export type BTDeviceListProps = Omit<
FlashListProps<DeviceListItem>,
"data" | "renderItem"
> & {
renderItem?: FlashListProps<DeviceListItem>["renderItem"];
itemProps?: ItemProps;
};
type ItemProps = Omit<TouchableOpacityProps, "onPress" | "children"> & {
onPress: (item: DeviceListItem, index: number) => void;
};
Features
🚀 High Performance
- FlashList Integration: Uses
@shopify/flash-listfor optimal rendering performance - Virtualization: Automatically handles item virtualization for large lists
- Memory Optimization: Efficient memory usage with item recycling
📱 Responsive Design
- Theme Integration: Fully integrated with app theme system
- Custom Styling: Comprehensive styling customization options
- Adaptive Layout: Responsive design for various screen sizes
🔗 Context Integration
- Device List Context: Automatically consumes device list from context
- Real-time Updates: Live status updates through context state management
- Type Safety: Full TypeScript support with comprehensive type definitions
Basic Usage
Simple Device List
import React from "react";
import { BTDeviceList, DeviceListProvider } from "@modules/bt-device";
const DeviceSelectionScreen = () => {
const handleDevicePress = (item: DeviceListItem, index: number) => {
console.log("Selected device:", item.deviceType);
// Navigate to device connection or detail screen
};
return (
<DeviceListProvider>
<BTDeviceList
itemProps={{
onPress: handleDevicePress,
}}
/>
</DeviceListProvider>
);
};
With Custom Styling
import React from "react";
import { View } from "react-native";
import { BTDeviceList, DeviceListProvider } from "@modules/bt-device";
const StyledDeviceList = () => {
return (
<DeviceListProvider>
<BTDeviceList
contentContainerStyle={{
padding: 16,
backgroundColor: "#f5f5f5",
}}
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
itemProps={{
style: {
marginHorizontal: 8,
borderRadius: 12,
elevation: 4,
},
onPress: (item, index) => {
console.log(`Pressed item ${index}:`, item.title);
},
}}
/>
</DeviceListProvider>
);
};
Advanced Usage
Custom Render Item
import React from "react";
import { Text, View } from "react-native";
import { BTDeviceList, BTDeviceItem } from "@modules/bt-device";
const CustomDeviceList = () => {
const renderCustomItem = ({ item, index }) => (
<View style={styles.customItem}>
<Text style={styles.deviceTitle}>{item.title}</Text>
<Text style={styles.deviceStatus}>{item.status}</Text>
<BTDeviceItem.Image uri={item.deviceImageUri} />
</View>
);
return (
<DeviceListProvider>
<BTDeviceList
renderItem={renderCustomItem}
numColumns={2} // Grid layout
ItemSeparatorComponent={() => <View style={{ height: 10 }} />}
/>
</DeviceListProvider>
);
};
With Pull-to-Refresh
import React, { useState, useCallback } from "react";
import { BTDeviceList, DeviceListProvider } from "@modules/bt-device";
const RefreshableDeviceList = () => {
const [refreshing, setRefreshing] = useState(false);
const handleRefresh = useCallback(async () => {
setRefreshing(true);
// Simulate API call or device scan
await new Promise((resolve) => setTimeout(resolve, 2000));
setRefreshing(false);
}, []);
return (
<DeviceListProvider>
<BTDeviceList
refreshing={refreshing}
onRefresh={handleRefresh}
itemProps={{
onPress: (item) => console.log("Device selected:", item.deviceType),
}}
/>
</DeviceListProvider>
);
};
Filtered Device List
import React, { useMemo } from "react";
import { BTDeviceList, useDeviceList } from "@modules/bt-device";
import { DeviceStatus } from "@modules/bt-management";
const FilteredDeviceList = ({
statusFilter,
}: {
statusFilter?: DeviceStatus;
}) => {
const { deviceListItems } = useDeviceList();
const filteredData = useMemo(() => {
if (!statusFilter) return deviceListItems;
return deviceListItems.filter((item) => item.status === statusFilter);
}, [deviceListItems, statusFilter]);
return (
<BTDeviceList
data={filteredData} // Override context data
itemProps={{
onPress: (item) => console.log("Filtered device:", item.deviceType),
}}
/>
);
};
// Usage with context provider
const App = () => (
<DeviceListProvider>
<FilteredDeviceList statusFilter={DeviceStatus.CONNECTED} />
</DeviceListProvider>
);
Default Item Component
The default item component includes:
Visual Structure
// Default item structure
<BTDeviceItem>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>{translatedTitle}</BTDeviceItem.Title>
<BTDeviceItem.SubTitle>{translatedSubtitle}</BTDeviceItem.SubTitle>
<BTDeviceItem.Status status={item.status} />
</BTDeviceItem.Content>
<BTDeviceItem.Image uri={item.deviceImageUri} />
</BTDeviceItem.TopContainer>
<BTDeviceItem.Divider />
<BTDeviceItem.BottomContainer>
<BTDeviceItem.SubTitle>Instructions</BTDeviceItem.SubTitle>
<ArrowRight2 size={20} color={theme.colors.onSurfaceDisabled} />
</BTDeviceItem.BottomContainer>
</BTDeviceItem>
Default Behavior
- Internationalization: Automatic translation of device titles and subtitles
- Status Indicators: Real-time status display with color coding
- Device Images: Optimized device image rendering
- Instructions Access: Visual indicator for instruction availability
- Theme Integration: Consistent styling with app theme
Performance Optimization
FlashList Configuration
<BTDeviceList
estimatedItemSize={177} // Pre-configured optimal size
removeClippedSubviews={true}
maxToRenderPerBatch={5}
updateCellsBatchingPeriod={100}
windowSize={10}
initialNumToRender={6}
getItemType={(item) => item.deviceType} // Optimize for device types
/>
Memory Management
const OptimizedDeviceList = () => {
const keyExtractor = useCallback(
(item: DeviceListItem, index: number) => `${item.deviceType}-${index}`,
[],
);
return (
<BTDeviceList
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={3}
itemProps={{
onPress: useCallback((item) => {
// Memoized press handler
}, []),
}}
/>
);
};
Styling and Theming
Theme Integration
import { useAppTheme } from "@modules/theme";
const ThemedDeviceList = () => {
const theme = useAppTheme();
return (
<BTDeviceList
contentContainerStyle={{
padding: theme.spacing(2),
backgroundColor: theme.colors.background,
}}
ItemSeparatorComponent={() => (
<View style={{ height: theme.spacing(2.5) }} />
)}
/>
);
};
Custom Item Styling
<BTDeviceList
itemProps={{
style: {
marginHorizontal: 16,
borderRadius: 12,
backgroundColor: "white",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
onPress: handleDevicePress,
}}
/>
Accessibility
Screen Reader Support
<BTDeviceList
itemProps={{
accessibilityRole: "button",
accessibilityLabel: (item) =>
`${item.title}, status: ${item.status}, tap to connect`,
onPress: handleDevicePress,
}}
/>
Keyboard Navigation
The component supports keyboard navigation when used with appropriate focus management:
import { useFocusEffect } from "@react-navigation/native";
const AccessibleDeviceList = () => {
const listRef = useRef<FlashList<DeviceListItem>>(null);
useFocusEffect(
useCallback(() => {
// Set focus to first item when screen gains focus
listRef.current?.scrollToIndex({ index: 0, animated: false });
}, []),
);
return (
<BTDeviceList
ref={listRef}
accessible={true}
accessibilityLabel="Medical device list"
/>
);
};
State Management Integration
With BT Management
import React, { useEffect } from "react";
import { BTProvider, DeviceStatus } from "@modules/bt-management";
import { BTDeviceList, useDeviceList } from "@modules/bt-device";
const IntegratedDeviceList = () => {
const { setDeviceListItems } = useDeviceList();
const handleDeviceStatusUpdate = (
status: DeviceStatus,
device: DeviceData,
) => {
setDeviceListItems((prev) =>
prev.map((item) =>
item.deviceType === device.name ? { ...item, status } : item,
),
);
};
return (
<BTProvider
onDeviceStatusUpdated={handleDeviceStatusUpdate}
// ... other BT provider props
>
<BTDeviceList
itemProps={{
onPress: (item) => {
// Connect to selected device
console.log("Connecting to:", item.deviceType);
},
}}
/>
</BTProvider>
);
};
External State Management
import { useSelector, useDispatch } from "react-redux";
import { BTDeviceList, useDeviceList } from "@modules/bt-device";
const ReduxDeviceList = () => {
const dispatch = useDispatch();
const selectedDevices = useSelector((state) => state.devices.selected);
const { setDeviceListItems } = useDeviceList();
useEffect(() => {
// Sync with Redux state
setDeviceListItems((prev) =>
prev.map((item) => ({
...item,
status: selectedDevices.includes(item.deviceType)
? DeviceStatus.CONNECTED
: DeviceStatus.DISCONNECTED,
})),
);
}, [selectedDevices, setDeviceListItems]);
return (
<BTDeviceList
itemProps={{
onPress: (item) => {
dispatch(selectDevice(item.deviceType));
},
}}
/>
);
};
Error Handling
Graceful Error States
import React, { useState } from "react";
import { Text, View } from "react-native";
import { BTDeviceList } from "@modules/bt-device";
const RobustDeviceList = () => {
const [error, setError] = useState<string | null>(null);
if (error) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Failed to load devices: {error}</Text>
<Button onPress={() => setError(null)} title="Retry" />
</View>
);
}
return (
<BTDeviceList
itemProps={{
onPress: (item) => {
try {
// Handle device selection
} catch (err) {
setError(err.message);
}
},
}}
onError={(error) => setError(error.message)}
/>
);
};
Testing
Component Testing
import React from "react";
import { render, fireEvent } from "@testing-library/react-native";
import { BTDeviceList, DeviceListProvider } from "@modules/bt-device";
const renderWithProvider = (component) => {
return render(<DeviceListProvider>{component}</DeviceListProvider>);
};
describe("BTDeviceList", () => {
it("renders device items correctly", () => {
const { getByText } = renderWithProvider(<BTDeviceList />);
expect(getByText(/Blood Pressure Monitor/)).toBeTruthy();
expect(getByText(/Glucose Meter/)).toBeTruthy();
});
it("calls onPress when item is pressed", () => {
const mockOnPress = jest.fn();
const { getByTestId } = renderWithProvider(
<BTDeviceList itemProps={{ onPress: mockOnPress }} />,
);
fireEvent.press(getByTestId("device-item-0"));
expect(mockOnPress).toHaveBeenCalledWith(
expect.objectContaining({
deviceType: expect.any(String),
}),
0,
);
});
it("applies custom styling", () => {
const customStyle = { backgroundColor: "red" };
const { getByTestId } = renderWithProvider(
<BTDeviceList testID="device-list" contentContainerStyle={customStyle} />,
);
const list = getByTestId("device-list");
expect(list.props.style).toContain(customStyle);
});
});
Integration Testing
import React from "react";
import { act, renderHook } from "@testing-library/react-hooks";
import { DeviceListProvider, useDeviceList } from "@modules/bt-device";
describe("BTDeviceList Integration", () => {
it("updates when context state changes", () => {
const wrapper = ({ children }) => (
<DeviceListProvider>{children}</DeviceListProvider>
);
const { result } = renderHook(() => useDeviceList(), { wrapper });
act(() => {
result.current.setDeviceListItems((prev) =>
prev.map((item) => ({ ...item, status: DeviceStatus.CONNECTED })),
);
});
expect(
result.current.deviceListItems.every(
(item) => item.status === DeviceStatus.CONNECTED,
),
).toBe(true);
});
});
Common Patterns
Healthcare Dashboard
const HealthcareDashboard = () => {
return (
<DeviceListProvider>
<ScrollView>
<Text style={styles.sectionTitle}>Available Devices</Text>
<BTDeviceList
scrollEnabled={false} // Disable internal scrolling
itemProps={{
onPress: (device) =>
navigation.navigate("DeviceDetail", { device }),
}}
/>
</ScrollView>
</DeviceListProvider>
);
};
Modal Device Selection
const DeviceSelectionModal = ({ visible, onSelect, onClose }) => {
return (
<Modal visible={visible} onRequestClose={onClose}>
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>Select Device</Text>
<Button title="Cancel" onPress={onClose} />
</View>
<DeviceListProvider>
<BTDeviceList
contentContainerStyle={{ flex: 1 }}
itemProps={{
onPress: (device) => {
onSelect(device);
onClose();
},
}}
/>
</DeviceListProvider>
</SafeAreaView>
</Modal>
);
};
Device Status Dashboard
const DeviceStatusDashboard = () => {
const connectedDevices = useDeviceList().deviceListItems.filter(
(item) => item.status === DeviceStatus.CONNECTED,
);
return (
<View style={styles.dashboard}>
<Text style={styles.title}>
Connected Devices ({connectedDevices.length})
</Text>
<DeviceListProvider>
<BTDeviceList
data={connectedDevices}
horizontal={true}
showsHorizontalScrollIndicator={false}
ItemSeparatorComponent={() => <View style={{ width: 12 }} />}
/>
</DeviceListProvider>
</View>
);
};
Best Practices
Performance
- Use KeyExtractor: Always provide a stable key extractor for optimal performance
- Memoize Callbacks: Memoize event handlers to prevent unnecessary re-renders
- Optimize Images: Use appropriately sized device images
- Batch Updates: Batch state updates when updating multiple devices
UX Design
- Loading States: Implement loading indicators for device scanning
- Empty States: Provide meaningful empty state messages
- Error Recovery: Allow users to retry failed operations
- Accessibility: Include proper accessibility labels and hints
Code Organization
- Context Usage: Always use DeviceListProvider for state management
- Type Safety: Leverage TypeScript for better development experience
- Testing: Write comprehensive tests for critical user flows
- Documentation: Document custom render functions and complex logic
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
renderItem | FlashListProps["renderItem"] | Default item | Custom item renderer |
itemProps | ItemProps | undefined | Props passed to default item component |
contentContainerStyle | ViewStyle | Theme padding | Container styling |
ItemSeparatorComponent | ComponentType | 10px height View | Component between items |
testID | string | undefined | Test identifier |
All other props from FlashListProps<DeviceListItem> are supported except data (from context) and renderItem (when using default).
ItemProps
| Prop | Type | Description |
|---|---|---|
onPress | (item: DeviceListItem, index: number) => void | Item press handler |
style | ViewStyle | Item container styling |
testID | string | Test identifier for items |
Plus all TouchableOpacityProps except onPress and children.
Dependencies
@shopify/flash-list: High-performance list componenticonsax-react-nativejs: Arrow icon for instructionsreact-i18next: Internationalization@modules/theme: Theme system@modules/bt-device/hooks/use-device-list: Device list context hook@modules/bt-device/components/item/btdevice-item: Default item component
Related Documentation
BTDeviceItem: Individual device item componentDeviceListProvider: Context provider for device list stateBT Management: Core Bluetooth device management