I am building a React Native (Expo) app that scans for Bluetooth devices. The Bluetooth API exposes a callback for when devices are detected, which I use to put non-duplicate devices into an array:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
The problem is that the callback is called many many times faster than setDeviceList
updates, so the duplicate checking doesn’t work (if I log deviceList
, it’s just empty).
If I use an additional, separate regular (non-useState) array, the duplicate checking works, but the state doesn’t update consistently:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
var deviceList2 = [];
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList2.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
deviceList2.push(newDevice);
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
This code almost works, but the deviceList
state doesn’t update correctly: it shows the first couple of devices but then doesn’t update again unless some other component causes a re-render.
What do I need to do to make this work as expected?
2
Answers
convert your callback to promise so that until you get completed device list, checkout below code (PS. not tested, please change as you need)
I would suggest wrap your duplicate check within the state set function itself, and then return the same device list if no new devices have been found. This offloads race condition handling to the underlying react implementation itself, which I’ve found to be good enough for most cases.
Thus it would look something like this:
Since
old
is unchanged if no new unique devices are found it will also skip next re-render according to the docs ( which is a neat optimisation 🙂 )This is the preferred way to implement state updates that are dependant on previous state according to the docs
https://reactjs.org/docs/hooks-reference.html#functional-updates