Hooks
No hooks found in any category.
useBluetooth
browserInstallation
npx usehooks-cli@latest add use-bluetooth
Description
A comprehensive hook for interacting with Bluetooth Low Energy devices using the Web Bluetooth API. Provides device discovery, connection management, and GATT characteristic operations.
Parameters
Name | Type | Default | Description |
---|---|---|---|
options? | UseBluetoothOptions | - | Default options for device requests |
Parameter Properties
options properties:
Name | Type | Description |
---|---|---|
filters? | BluetoothLEScanFilter[] | Filters to apply when scanning for devices |
optionalServices? | BluetoothServiceUUID[] | Optional services to access on the device |
acceptAllDevices? | boolean | Whether to accept all devices (used when no filters specified) |
Return Type
UseBluetoothReturn
Property | Type | Description |
---|---|---|
device | BluetoothDeviceInfo | null | Information about the currently selected device |
isSupported | boolean | Whether Web Bluetooth API is supported |
isAvailable | boolean | null | Whether Bluetooth is available on the device |
isConnecting | boolean | Whether a connection attempt is in progress |
error | string | null | Error message if any operation failed |
requestDevice | (options?: BluetoothRequestDeviceOptions) => Promise<BluetoothDeviceInfo | null> | Request access to a Bluetooth device |
connect | () => Promise<boolean> | Connect to the selected device |
disconnect | () => Promise<void> | Disconnect from the current device |
readCharacteristic | (serviceUuid: BluetoothServiceUUID, characteristicUuid: BluetoothServiceUUID) => Promise<DataView | null> | Read a value from a GATT characteristic |
writeCharacteristic | (serviceUuid: BluetoothServiceUUID, characteristicUuid: BluetoothServiceUUID, value: BufferSource) => Promise<boolean> | Write a value to a GATT characteristic |
startNotifications | (serviceUuid: BluetoothServiceUUID, characteristicUuid: BluetoothServiceUUID, callback: (value: DataView) => void) => Promise<boolean> | Start receiving notifications from a characteristic |
stopNotifications | (serviceUuid: BluetoothServiceUUID, characteristicUuid: BluetoothServiceUUID) => Promise<boolean> | Stop receiving notifications from a characteristic |
Examples
Basic Device Connection
Connect to a Bluetooth device and check connection status
1import { useBluetooth } from '@usehooks/use-bluetooth';
2
3function BluetoothConnector() {
4 const {
5 device,
6 isSupported,
7 isConnecting,
8 error,
9 requestDevice,
10 connect,
11 disconnect
12 } = useBluetooth();
13
14 const handleRequestDevice = async () => {
15 await requestDevice({
16 acceptAllDevices: true
17 });
18 };
19
20 const handleConnect = async () => {
21 const success = await connect();
22 if (success) {
23 console.log('Connected successfully');
24 }
25 };
26
27 if (!isSupported) {
28 return <div>Web Bluetooth not supported</div>;
29 }
30
31 return (
32 <div>
33 <button onClick={handleRequestDevice} disabled={isConnecting}>
34 Select Device
35 </button>
36
37 {device && (
38 <div>
39 <p>Device: {device.name || 'Unknown'}</p>
40 <p>Status: {device.connected ? 'Connected' : 'Disconnected'}</p>
41
42 {!device.connected ? (
43 <button onClick={handleConnect} disabled={isConnecting}>
44 {isConnecting ? 'Connecting...' : 'Connect'}
45 </button>
46 ) : (
47 <button onClick={disconnect}>Disconnect</button>
48 )}
49 </div>
50 )}
51
52 {error && <p>Error: {error}</p>}
53 </div>
54 );
55}
Heart Rate Monitor
Connect to a heart rate monitor and read heart rate data
1import { useBluetooth } from '@usehooks/use-bluetooth';
2import { useState, useEffect } from 'react';
3
4function HeartRateMonitor() {
5 const {
6 device,
7 requestDevice,
8 connect,
9 readCharacteristic,
10 startNotifications
11 } = useBluetooth();
12 const [heartRate, setHeartRate] = useState<number | null>(null);
13
14 const connectToHeartRateMonitor = async () => {
15 // Request heart rate monitor
16 await requestDevice({
17 filters: [{ services: ['heart_rate'] }]
18 });
19 };
20
21 useEffect(() => {
22 if (device?.connected) {
23 // Start heart rate notifications
24 startNotifications(
25 'heart_rate',
26 'heart_rate_measurement',
27 (value) => {
28 // Parse heart rate value (first byte after flags)
29 const rate = value.getUint8(1);
30 setHeartRate(rate);
31 }
32 );
33 }
34 }, [device?.connected, startNotifications]);
35
36 return (
37 <div>
38 <button onClick={connectToHeartRateMonitor}>
39 Connect Heart Rate Monitor
40 </button>
41
42 {device && (
43 <div>
44 <p>Device: {device.name}</p>
45 {!device.connected ? (
46 <button onClick={connect}>Connect</button>
47 ) : (
48 <div>
49 <p>Status: Connected</p>
50 {heartRate && (
51 <p>Heart Rate: {heartRate} BPM</p>
52 )}
53 </div>
54 )}
55 </div>
56 )}
57 </div>
58 );
59}
Custom Service Communication
Read from and write to custom GATT characteristics
1import { useBluetooth } from '@usehooks/use-bluetooth';
2import { useState } from 'react';
3
4function CustomDeviceController() {
5 const {
6 device,
7 requestDevice,
8 connect,
9 readCharacteristic,
10 writeCharacteristic
11 } = useBluetooth();
12 const [data, setData] = useState<string>('');
13 const [inputValue, setInputValue] = useState('');
14
15 const SERVICE_UUID = '12345678-1234-1234-1234-123456789abc';
16 const READ_CHAR_UUID = '87654321-4321-4321-4321-cba987654321';
17 const WRITE_CHAR_UUID = '11111111-2222-3333-4444-555555555555';
18
19 const connectToDevice = async () => {
20 await requestDevice({
21 filters: [{ services: [SERVICE_UUID] }],
22 optionalServices: [SERVICE_UUID]
23 });
24 };
25
26 const readData = async () => {
27 const value = await readCharacteristic(SERVICE_UUID, READ_CHAR_UUID);
28 if (value) {
29 const decoder = new TextDecoder();
30 const text = decoder.decode(value);
31 setData(text);
32 }
33 };
34
35 const writeData = async () => {
36 const encoder = new TextEncoder();
37 const data = encoder.encode(inputValue);
38 const success = await writeCharacteristic(SERVICE_UUID, WRITE_CHAR_UUID, data);
39 if (success) {
40 console.log('Data written successfully');
41 setInputValue('');
42 }
43 };
44
45 return (
46 <div>
47 <button onClick={connectToDevice}>Select Device</button>
48
49 {device && !device.connected && (
50 <button onClick={connect}>Connect</button>
51 )}
52
53 {device?.connected && (
54 <div>
55 <button onClick={readData}>Read Data</button>
56 <p>Read Value: {data}</p>
57
58 <div>
59 <input
60 value={inputValue}
61 onChange={(e) => setInputValue(e.target.value)}
62 placeholder="Enter data to write"
63 />
64 <button onClick={writeData}>Write Data</button>
65 </div>
66 </div>
67 )}
68 </div>
69 );
70}
Dependencies
react
Notes
- •Only available in browsers that support Web Bluetooth API (Chrome 56+, Edge 79+)
- •Requires HTTPS in production environments
- •User gesture required to initiate device requests
- •Device permissions are persistent across sessions
- •Automatically handles disconnection events and cleanup
- •GATT characteristics are cached for performance
Implementation
1"use client";
2
3import { useState, useCallback, useRef, useEffect } from "react";
4
5// Extend Navigator interface for Web Bluetooth API
6declare global {
7 interface Navigator {
8 bluetooth?: {
9 requestDevice(options: BluetoothRequestDeviceOptions): Promise<{
10 id: string;
11 name: string | undefined;
12 gatt?: {
13 connect(): Promise<any>;
14 connected: boolean;
15 };
16 addEventListener(
17 type: string,
18 listener: EventListener,
19 options?: boolean | AddEventListenerOptions
20 ): void;
21 }>;
22 getAvailability(): Promise<boolean>;
23 };
24 }
25}
26
27interface BluetoothRequestDeviceOptions {
28 filters?: BluetoothLEScanFilter[];
29 optionalServices?: BluetoothServiceUUID[];
30 acceptAllDevices?: boolean;
31}
32
33interface BluetoothLEScanFilter {
34 services?: BluetoothServiceUUID[];
35 name?: string;
36 namePrefix?: string;
37 manufacturerData?: BluetoothManufacturerDataFilter[];
38}
39
40interface BluetoothManufacturerDataFilter {
41 companyIdentifier: number;
42 dataPrefix?: BufferSource;
43 mask?: BufferSource;
44}
45
46type BluetoothServiceUUID = number | string;
47
48interface BluetoothDeviceInfo {
49 id: string;
50 name: string | undefined;
51 connected: boolean;
52}
53
54interface UseBluetoothOptions {
55 filters?: BluetoothLEScanFilter[];
56 optionalServices?: BluetoothServiceUUID[];
57 acceptAllDevices?: boolean;
58}
59
60interface UseBluetoothReturn {
61 device: BluetoothDeviceInfo | null;
62 isSupported: boolean;
63 isAvailable: boolean | null;
64 isConnecting: boolean;
65 error: string | null;
66 requestDevice: (
67 options?: BluetoothRequestDeviceOptions
68 ) => Promise<BluetoothDeviceInfo | null>;
69 connect: () => Promise<boolean>;
70 disconnect: () => Promise<void>;
71 readCharacteristic: (
72 serviceUuid: BluetoothServiceUUID,
73 characteristicUuid: BluetoothServiceUUID
74 ) => Promise<DataView | null>;
75 writeCharacteristic: (
76 serviceUuid: BluetoothServiceUUID,
77 characteristicUuid: BluetoothServiceUUID,
78 value: BufferSource
79 ) => Promise<boolean>;
80 startNotifications: (
81 serviceUuid: BluetoothServiceUUID,
82 characteristicUuid: BluetoothServiceUUID,
83 callback: (value: DataView) => void
84 ) => Promise<boolean>;
85 stopNotifications: (
86 serviceUuid: BluetoothServiceUUID,
87 characteristicUuid: BluetoothServiceUUID
88 ) => Promise<boolean>;
89}
90
91export const useBluetooth = (
92 options: UseBluetoothOptions = {}
93): UseBluetoothReturn => {
94 const [device, setDevice] = useState<BluetoothDeviceInfo | null>(null);
95 const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
96 const [isConnecting, setIsConnecting] = useState(false);
97 const [error, setError] = useState<string | null>(null);
98
99 const deviceRef = useRef<{
100 id: string;
101 name: string | undefined;
102 gatt?: {
103 connect(): Promise<any>;
104 connected: boolean;
105 };
106 addEventListener(
107 type: string,
108 listener: EventListener,
109 options?: boolean | AddEventListenerOptions
110 ): void;
111 } | null>(null);
112 const serverRef = useRef<any | null>(null);
113 const characteristicsRef = useRef<Map<string, any>>(new Map());
114
115 // Check if Web Bluetooth API is supported
116 const isSupported =
117 typeof navigator !== "undefined" && "bluetooth" in navigator;
118
119 // Check Bluetooth availability
120 useEffect(() => {
121 if (isSupported && navigator.bluetooth?.getAvailability) {
122 navigator.bluetooth
123 .getAvailability()
124 .then(setIsAvailable)
125 .catch(() => setIsAvailable(false));
126 } else {
127 setIsAvailable(false);
128 }
129 }, [isSupported]);
130
131 // Request Bluetooth device
132 const requestDevice = useCallback(
133 async (
134 requestOptions?: BluetoothRequestDeviceOptions
135 ): Promise<BluetoothDeviceInfo | null> => {
136 if (!isSupported || !navigator.bluetooth) {
137 setError("Web Bluetooth API is not supported");
138 return null;
139 }
140
141 try {
142 setError(null);
143 const deviceOptions = requestOptions || {
144 filters: options.filters,
145 optionalServices: options.optionalServices,
146 acceptAllDevices: options.acceptAllDevices,
147 };
148
149 // Ensure we have either filters or acceptAllDevices
150 if (!deviceOptions.filters && !deviceOptions.acceptAllDevices) {
151 deviceOptions.acceptAllDevices = true;
152 }
153
154 const bluetoothDevice =
155 await navigator.bluetooth.requestDevice(deviceOptions);
156
157 deviceRef.current = bluetoothDevice;
158 const deviceInfo: BluetoothDeviceInfo = {
159 id: bluetoothDevice.id,
160 name: bluetoothDevice.name,
161 connected: bluetoothDevice.gatt?.connected || false,
162 };
163
164 setDevice(deviceInfo);
165
166 // Listen for disconnection
167 bluetoothDevice.addEventListener("gattserverdisconnected", () => {
168 setDevice((prev) => (prev ? { ...prev, connected: false } : null));
169 serverRef.current = null;
170 characteristicsRef.current.clear();
171 });
172
173 return deviceInfo;
174 } catch (err) {
175 const errorMessage =
176 err instanceof Error ? err.message : "Failed to request device";
177 setError(errorMessage);
178 return null;
179 }
180 },
181 [isSupported, options]
182 );
183
184 // Connect to device
185 const connect = useCallback(async (): Promise<boolean> => {
186 if (!deviceRef.current) {
187 setError("No device selected");
188 return false;
189 }
190
191 try {
192 setIsConnecting(true);
193 setError(null);
194
195 const server = await deviceRef.current.gatt?.connect();
196 if (server) {
197 serverRef.current = server;
198 setDevice((prev) => (prev ? { ...prev, connected: true } : null));
199 return true;
200 }
201
202 setError("Failed to connect to GATT server");
203 return false;
204 } catch (err) {
205 const errorMessage =
206 err instanceof Error ? err.message : "Failed to connect";
207 setError(errorMessage);
208 return false;
209 } finally {
210 setIsConnecting(false);
211 }
212 }, []);
213
214 // Disconnect from device
215 const disconnect = useCallback(async (): Promise<void> => {
216 if (serverRef.current && serverRef.current.connected) {
217 serverRef.current.disconnect();
218 }
219 serverRef.current = null;
220 characteristicsRef.current.clear();
221 setDevice((prev) => (prev ? { ...prev, connected: false } : null));
222 }, []);
223
224 // Get characteristic helper
225 const getCharacteristic = useCallback(
226 async (
227 serviceUuid: BluetoothServiceUUID,
228 characteristicUuid: BluetoothServiceUUID
229 ): Promise<any | null> => {
230 if (!serverRef.current) {
231 setError("Not connected to device");
232 return null;
233 }
234
235 const key = `${serviceUuid}-${characteristicUuid}`;
236
237 if (characteristicsRef.current.has(key)) {
238 return characteristicsRef.current.get(key)!;
239 }
240
241 try {
242 const service = await serverRef.current.getPrimaryService(serviceUuid);
243 const characteristic =
244 await service.getCharacteristic(characteristicUuid);
245 characteristicsRef.current.set(key, characteristic);
246 return characteristic;
247 } catch (err) {
248 const errorMessage =
249 err instanceof Error ? err.message : "Failed to get characteristic";
250 setError(errorMessage);
251 return null;
252 }
253 },
254 []
255 );
256
257 // Read characteristic value
258 const readCharacteristic = useCallback(
259 async (
260 serviceUuid: BluetoothServiceUUID,
261 characteristicUuid: BluetoothServiceUUID
262 ): Promise<DataView | null> => {
263 const characteristic = await getCharacteristic(
264 serviceUuid,
265 characteristicUuid
266 );
267 if (!characteristic) return null;
268
269 try {
270 setError(null);
271 const value = await characteristic.readValue();
272 return value;
273 } catch (err) {
274 const errorMessage =
275 err instanceof Error ? err.message : "Failed to read characteristic";
276 setError(errorMessage);
277 return null;
278 }
279 },
280 [getCharacteristic]
281 );
282
283 // Write characteristic value
284 const writeCharacteristic = useCallback(
285 async (
286 serviceUuid: BluetoothServiceUUID,
287 characteristicUuid: BluetoothServiceUUID,
288 value: BufferSource
289 ): Promise<boolean> => {
290 const characteristic = await getCharacteristic(
291 serviceUuid,
292 characteristicUuid
293 );
294 if (!characteristic) return false;
295
296 try {
297 setError(null);
298 await characteristic.writeValue(value);
299 return true;
300 } catch (err) {
301 const errorMessage =
302 err instanceof Error ? err.message : "Failed to write characteristic";
303 setError(errorMessage);
304 return false;
305 }
306 },
307 [getCharacteristic]
308 );
309
310 // Start notifications
311 const startNotifications = useCallback(
312 async (
313 serviceUuid: BluetoothServiceUUID,
314 characteristicUuid: BluetoothServiceUUID,
315 callback: (value: DataView) => void
316 ): Promise<boolean> => {
317 const characteristic = await getCharacteristic(
318 serviceUuid,
319 characteristicUuid
320 );
321 if (!characteristic) return false;
322
323 try {
324 setError(null);
325 await characteristic.startNotifications();
326
327 const handleNotification = (event: Event) => {
328 const target = event.target as { value?: DataView };
329 if (target.value) {
330 callback(target.value);
331 }
332 };
333
334 characteristic.addEventListener(
335 "characteristicvaluechanged",
336 handleNotification
337 );
338 return true;
339 } catch (err) {
340 const errorMessage =
341 err instanceof Error ? err.message : "Failed to start notifications";
342 setError(errorMessage);
343 return false;
344 }
345 },
346 [getCharacteristic]
347 );
348
349 // Stop notifications
350 const stopNotifications = useCallback(
351 async (
352 serviceUuid: BluetoothServiceUUID,
353 characteristicUuid: BluetoothServiceUUID
354 ): Promise<boolean> => {
355 const characteristic = await getCharacteristic(
356 serviceUuid,
357 characteristicUuid
358 );
359 if (!characteristic) return false;
360
361 try {
362 setError(null);
363 await characteristic.stopNotifications();
364 return true;
365 } catch (err) {
366 const errorMessage =
367 err instanceof Error ? err.message : "Failed to stop notifications";
368 setError(errorMessage);
369 return false;
370 }
371 },
372 [getCharacteristic]
373 );
374
375 // Cleanup on unmount
376 useEffect(() => {
377 return () => {
378 if (serverRef.current && serverRef.current.connected) {
379 serverRef.current.disconnect();
380 }
381 };
382 }, []);
383
384 return {
385 device,
386 isSupported,
387 isAvailable,
388 isConnecting,
389 error,
390 requestDevice,
391 connect,
392 disconnect,
393 readCharacteristic,
394 writeCharacteristic,
395 startNotifications,
396 stopNotifications,
397 };
398};
399