Hooks
No hooks found in any category.
useBluetooth
browserInstallation
npx usehooks-cli@latest add use-bluetoothDescription
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
5type BluetoothServiceUUID = number | string;
6
7interface BluetoothManufacturerDataFilter {
8 companyIdentifier: number;
9 dataPrefix?: BufferSource;
10 mask?: BufferSource;
11}
12
13interface BluetoothLEScanFilter {
14 services?: BluetoothServiceUUID[];
15 name?: string;
16 namePrefix?: string;
17 manufacturerData?: BluetoothManufacturerDataFilter[];
18}
19
20type BluetoothRequestDeviceOptions =
21 | {
22 filters: BluetoothLEScanFilter[];
23 optionalServices?: BluetoothServiceUUID[];
24 acceptAllDevices?: false;
25 }
26 | {
27 acceptAllDevices: boolean;
28 optionalServices?: BluetoothServiceUUID[];
29 filters?: undefined;
30 };
31
32interface BluetoothRemoteGATTCharacteristic {
33 value?: DataView | null;
34 readValue(): Promise<DataView>;
35 writeValue(value: BufferSource): Promise<void>;
36 startNotifications(): Promise<BluetoothRemoteGATTCharacteristic>;
37 stopNotifications(): Promise<BluetoothRemoteGATTCharacteristic>;
38 addEventListener(
39 type: string,
40 listener: EventListenerOrEventListenerObject,
41 options?: boolean | AddEventListenerOptions
42 ): void;
43}
44
45interface BluetoothRemoteGATTService {
46 getCharacteristic(
47 characteristicUuid: BluetoothServiceUUID
48 ): Promise<BluetoothRemoteGATTCharacteristic>;
49}
50
51interface BluetoothRemoteGATTServer {
52 connected: boolean;
53 disconnect(): void;
54 getPrimaryService(
55 serviceUuid: BluetoothServiceUUID
56 ): Promise<BluetoothRemoteGATTService>;
57}
58
59interface BluetoothRemoteGATT {
60 connected: boolean;
61 connect(): Promise<BluetoothRemoteGATTServer>;
62}
63
64interface BluetoothDevice {
65 id: string;
66 name?: string;
67 gatt?: BluetoothRemoteGATT;
68 addEventListener(
69 type: string,
70 listener: EventListenerOrEventListenerObject,
71 options?: boolean | AddEventListenerOptions
72 ): void;
73}
74
75interface BluetoothAPI {
76 requestDevice(options: BluetoothRequestDeviceOptions): Promise<BluetoothDevice>;
77 getAvailability?(): Promise<boolean>;
78}
79
80type NavigatorWithBluetooth = Navigator & {
81 bluetooth?: BluetoothAPI;
82};
83
84interface BluetoothDeviceInfo {
85 id: string;
86 name: string | undefined;
87 connected: boolean;
88}
89
90interface UseBluetoothOptions {
91 filters?: BluetoothLEScanFilter[];
92 optionalServices?: BluetoothServiceUUID[];
93 acceptAllDevices?: boolean;
94}
95
96interface UseBluetoothReturn {
97 device: BluetoothDeviceInfo | null;
98 isSupported: boolean;
99 isAvailable: boolean | null;
100 isConnecting: boolean;
101 error: string | null;
102 requestDevice: (
103 options?: BluetoothRequestDeviceOptions
104 ) => Promise<BluetoothDeviceInfo | null>;
105 connect: () => Promise<boolean>;
106 disconnect: () => Promise<void>;
107 readCharacteristic: (
108 serviceUuid: BluetoothServiceUUID,
109 characteristicUuid: BluetoothServiceUUID
110 ) => Promise<DataView | null>;
111 writeCharacteristic: (
112 serviceUuid: BluetoothServiceUUID,
113 characteristicUuid: BluetoothServiceUUID,
114 value: BufferSource
115 ) => Promise<boolean>;
116 startNotifications: (
117 serviceUuid: BluetoothServiceUUID,
118 characteristicUuid: BluetoothServiceUUID,
119 callback: (value: DataView) => void
120 ) => Promise<boolean>;
121 stopNotifications: (
122 serviceUuid: BluetoothServiceUUID,
123 characteristicUuid: BluetoothServiceUUID
124 ) => Promise<boolean>;
125}
126
127export const useBluetooth = (
128 options: UseBluetoothOptions = {}
129): UseBluetoothReturn => {
130 const [device, setDevice] = useState<BluetoothDeviceInfo | null>(null);
131 const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
132 const [isConnecting, setIsConnecting] = useState(false);
133 const [error, setError] = useState<string | null>(null);
134
135 const deviceRef = useRef<BluetoothDevice | null>(null);
136 const serverRef = useRef<BluetoothRemoteGATTServer | null>(null);
137 const characteristicsRef =
138 useRef<Map<string, BluetoothRemoteGATTCharacteristic>>(new Map());
139
140 // Check if Web Bluetooth API is supported
141 const isSupported =
142 typeof navigator !== "undefined" &&
143 typeof (navigator as NavigatorWithBluetooth).bluetooth !== "undefined";
144
145 // Check Bluetooth availability
146 useEffect(() => {
147 const bluetooth =
148 typeof navigator === "undefined"
149 ? undefined
150 : (navigator as NavigatorWithBluetooth).bluetooth;
151
152 if (isSupported && bluetooth?.getAvailability) {
153 bluetooth
154 .getAvailability()
155 .then(setIsAvailable)
156 .catch(() => setIsAvailable(false));
157 } else {
158 setIsAvailable(false);
159 }
160 }, [isSupported]);
161
162 // Request Bluetooth device
163 const requestDevice = useCallback(
164 async (
165 requestOptions?: BluetoothRequestDeviceOptions
166 ): Promise<BluetoothDeviceInfo | null> => {
167 if (!isSupported) {
168 setError("Web Bluetooth API is not supported");
169 return null;
170 }
171
172 const bluetooth = (navigator as NavigatorWithBluetooth).bluetooth;
173 if (!bluetooth) {
174 setError("Web Bluetooth API is not supported");
175 return null;
176 }
177
178 try {
179 setError(null);
180 const deviceOptions: BluetoothRequestDeviceOptions = requestOptions
181 ? requestOptions
182 : options.filters && options.filters.length > 0
183 ? {
184 filters: options.filters,
185 optionalServices: options.optionalServices,
186 }
187 : {
188 acceptAllDevices: options.acceptAllDevices ?? true,
189 optionalServices: options.optionalServices,
190 };
191
192 const bluetoothDevice = await bluetooth.requestDevice(deviceOptions);
193
194 deviceRef.current = bluetoothDevice;
195 const deviceInfo: BluetoothDeviceInfo = {
196 id: bluetoothDevice.id,
197 name: bluetoothDevice.name ?? undefined,
198 connected: bluetoothDevice.gatt?.connected || false,
199 };
200
201 setDevice(deviceInfo);
202
203 // Listen for disconnection
204 bluetoothDevice.addEventListener("gattserverdisconnected", () => {
205 setDevice((prev) => (prev ? { ...prev, connected: false } : null));
206 serverRef.current = null;
207 characteristicsRef.current.clear();
208 });
209
210 return deviceInfo;
211 } catch (err) {
212 const errorMessage =
213 err instanceof Error ? err.message : "Failed to request device";
214 setError(errorMessage);
215 return null;
216 }
217 },
218 [isSupported, options]
219 );
220
221 // Connect to device
222 const connect = useCallback(async (): Promise<boolean> => {
223 if (!deviceRef.current) {
224 setError("No device selected");
225 return false;
226 }
227
228 try {
229 setIsConnecting(true);
230 setError(null);
231
232 const server = await deviceRef.current.gatt?.connect();
233 if (server) {
234 serverRef.current = server;
235 setDevice((prev) => (prev ? { ...prev, connected: true } : null));
236 return true;
237 }
238
239 setError("Failed to connect to GATT server");
240 return false;
241 } catch (err) {
242 const errorMessage =
243 err instanceof Error ? err.message : "Failed to connect";
244 setError(errorMessage);
245 return false;
246 } finally {
247 setIsConnecting(false);
248 }
249 }, []);
250
251 // Disconnect from device
252 const disconnect = useCallback(async (): Promise<void> => {
253 if (serverRef.current && serverRef.current.connected) {
254 serverRef.current.disconnect();
255 }
256 serverRef.current = null;
257 characteristicsRef.current.clear();
258 setDevice((prev) => (prev ? { ...prev, connected: false } : null));
259 }, []);
260
261 // Get characteristic helper
262 const getCharacteristic = useCallback(
263 async (
264 serviceUuid: BluetoothServiceUUID,
265 characteristicUuid: BluetoothServiceUUID
266 ): Promise<BluetoothRemoteGATTCharacteristic | null> => {
267 if (!serverRef.current) {
268 setError("Not connected to device");
269 return null;
270 }
271
272 const key = `${serviceUuid}-${characteristicUuid}`;
273
274 if (characteristicsRef.current.has(key)) {
275 return characteristicsRef.current.get(key)!;
276 }
277
278 try {
279 const service = await serverRef.current.getPrimaryService(serviceUuid);
280 const characteristic =
281 await service.getCharacteristic(characteristicUuid);
282 characteristicsRef.current.set(key, characteristic);
283 return characteristic;
284 } catch (err) {
285 const errorMessage =
286 err instanceof Error ? err.message : "Failed to get characteristic";
287 setError(errorMessage);
288 return null;
289 }
290 },
291 []
292 );
293
294 // Read characteristic value
295 const readCharacteristic = useCallback(
296 async (
297 serviceUuid: BluetoothServiceUUID,
298 characteristicUuid: BluetoothServiceUUID
299 ): Promise<DataView | null> => {
300 const characteristic = await getCharacteristic(
301 serviceUuid,
302 characteristicUuid
303 );
304 if (!characteristic) return null;
305
306 try {
307 setError(null);
308 const value = await characteristic.readValue();
309 return value;
310 } catch (err) {
311 const errorMessage =
312 err instanceof Error ? err.message : "Failed to read characteristic";
313 setError(errorMessage);
314 return null;
315 }
316 },
317 [getCharacteristic]
318 );
319
320 // Write characteristic value
321 const writeCharacteristic = useCallback(
322 async (
323 serviceUuid: BluetoothServiceUUID,
324 characteristicUuid: BluetoothServiceUUID,
325 value: BufferSource
326 ): Promise<boolean> => {
327 const characteristic = await getCharacteristic(
328 serviceUuid,
329 characteristicUuid
330 );
331 if (!characteristic) return false;
332
333 try {
334 setError(null);
335 await characteristic.writeValue(value);
336 return true;
337 } catch (err) {
338 const errorMessage =
339 err instanceof Error ? err.message : "Failed to write characteristic";
340 setError(errorMessage);
341 return false;
342 }
343 },
344 [getCharacteristic]
345 );
346
347 // Start notifications
348 const startNotifications = useCallback(
349 async (
350 serviceUuid: BluetoothServiceUUID,
351 characteristicUuid: BluetoothServiceUUID,
352 callback: (value: DataView) => void
353 ): Promise<boolean> => {
354 const characteristic = await getCharacteristic(
355 serviceUuid,
356 characteristicUuid
357 );
358 if (!characteristic) return false;
359
360 try {
361 setError(null);
362 await characteristic.startNotifications();
363
364 const handleNotification = (event: Event) => {
365 const target = event.target as BluetoothRemoteGATTCharacteristic | null;
366 if (target?.value) {
367 callback(target.value);
368 }
369 };
370
371 characteristic.addEventListener(
372 "characteristicvaluechanged",
373 handleNotification
374 );
375 return true;
376 } catch (err) {
377 const errorMessage =
378 err instanceof Error ? err.message : "Failed to start notifications";
379 setError(errorMessage);
380 return false;
381 }
382 },
383 [getCharacteristic]
384 );
385
386 // Stop notifications
387 const stopNotifications = useCallback(
388 async (
389 serviceUuid: BluetoothServiceUUID,
390 characteristicUuid: BluetoothServiceUUID
391 ): Promise<boolean> => {
392 const characteristic = await getCharacteristic(
393 serviceUuid,
394 characteristicUuid
395 );
396 if (!characteristic) return false;
397
398 try {
399 setError(null);
400 await characteristic.stopNotifications();
401 return true;
402 } catch (err) {
403 const errorMessage =
404 err instanceof Error ? err.message : "Failed to stop notifications";
405 setError(errorMessage);
406 return false;
407 }
408 },
409 [getCharacteristic]
410 );
411
412 // Cleanup on unmount
413 useEffect(() => {
414 return () => {
415 if (serverRef.current && serverRef.current.connected) {
416 serverRef.current.disconnect();
417 }
418 };
419 }, []);
420
421 return {
422 device,
423 isSupported,
424 isAvailable,
425 isConnecting,
426 error,
427 requestDevice,
428 connect,
429 disconnect,
430 readCharacteristic,
431 writeCharacteristic,
432 startNotifications,
433 stopNotifications,
434 };
435};
436