Skip to main content

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-list for 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>
);
};
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

  1. Use KeyExtractor: Always provide a stable key extractor for optimal performance
  2. Memoize Callbacks: Memoize event handlers to prevent unnecessary re-renders
  3. Optimize Images: Use appropriately sized device images
  4. Batch Updates: Batch state updates when updating multiple devices

UX Design

  1. Loading States: Implement loading indicators for device scanning
  2. Empty States: Provide meaningful empty state messages
  3. Error Recovery: Allow users to retry failed operations
  4. Accessibility: Include proper accessibility labels and hints

Code Organization

  1. Context Usage: Always use DeviceListProvider for state management
  2. Type Safety: Leverage TypeScript for better development experience
  3. Testing: Write comprehensive tests for critical user flows
  4. Documentation: Document custom render functions and complex logic

API Reference

Props

PropTypeDefaultDescription
renderItemFlashListProps["renderItem"]Default itemCustom item renderer
itemPropsItemPropsundefinedProps passed to default item component
contentContainerStyleViewStyleTheme paddingContainer styling
ItemSeparatorComponentComponentType10px height ViewComponent between items
testIDstringundefinedTest identifier

All other props from FlashListProps<DeviceListItem> are supported except data (from context) and renderItem (when using default).

ItemProps

PropTypeDescription
onPress(item: DeviceListItem, index: number) => voidItem press handler
styleViewStyleItem container styling
testIDstringTest identifier for items

Plus all TouchableOpacityProps except onPress and children.

Dependencies

  • @shopify/flash-list: High-performance list component
  • iconsax-react-nativejs: Arrow icon for instructions
  • react-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