I am working on the react native sdk for socket mobile and for some reason I am unable to reliably set state with useState in my react native application. The state change happens in a callback that I pass to Socket Mobile's capture library. Sample app can be found here (for some reason hyperlinks aren't working).
https://github.com/SocketMobile/singleentry-rn/blob/main/App.js
The callback, onCaptureEvent, listens for various capture event types. Based on these event types, we manage the state accordingly. One way we do that is when there is a DeviceArrival event (when a scanner connects to our iPad/Android Tablet), we then open the device so we can see it's properties, change those properties, etc.
We also try to update a list of devices that come in. So if a device "arrives", we add it to a list and then update the state like so.
setDevices(prevDevices => {
prevDevices = prevDevices || [];
prevDevices.push({
guid,
name,
handle: newDevice.clientOrDeviceHandle,
device: newDevice,
});
return [...prevDevices];
});
This works for the first time, but then it doesn't work properly and the remove device function we have is unable to find the right device to remove (because either an old device is left behind or there is no devices in the list at all).
I have also tried doing it this way...
setDeviceList((prevDevices) => [...prevDevices, newDevice]);
This way we're not mutating the state value directly. However, when I do it this way, it doesn't work at all. When I try to find the device in the device list to remove it, it shows an error saying it cannot be found.
How or why am I unable to consistently update the list? I've looked at a number of tuts and it seems like the second way should work. I am assuming it has something to do with the fact that it is a callback function but we're not sure of a way around it other than this way.
We have also tried using useCallback for onCaptureEvent as well as making it asynchronous so we can use await in the function. Some state items do update accordingly (we have a state value status in the example) but maybe since it's a string it's easier to detect/persist the state change?
Below is the portion of the onCaptureEvent function that we think is the culprit.
const onCaptureEvent = useCallback(
(e, handle) => {
if (!e) {
return;
}
myLogger.log(`onCaptureEvent from ${handle}: `, e);
switch (e.id) {
case CaptureEventIds.DeviceArrival:
const newDevice = new CaptureRn();
const {guid, name} = e.value;
newDevice
.openDevice(guid, capture)
.then(result => {
myLogger.log('opening a device returns: ', result);
setStatus(`result of opening ${e.value.name} : ${result}`);
setDevices(prevDevices => {
prevDevices = prevDevices || [];
prevDevices.push({
guid,
name,
handle: newDevice.clientOrDeviceHandle,
device: newDevice,
});
return [...prevDevices];
});
})
.catch(err => {
myLogger.log(err);
setStatus(`error opening a device: ${err}`);
});
break;
...
}
I am using "react": "17.0.2" and "react-native": "0.68.2"
UPDATE 07/11/23
I discovered that when I console.log the devices just before the return statement of the component's UI, the device list is reflected correctly. I tested this with a useEffect that gets triggered whenever devices gets updated. See below.
useEffect(() => {
console.log('USE EFFECT: ', devices);
}, [devices]);
This ALSO logs the updated device list. It seems the only place where I am unable to keep track of the updated list is in the onCaptureEvent function. I'm guessing this is because onCaptureEvent is a callback that is not directly invoked by the RN component. Any ideas around this? I've used helper functions within the component outside of the onCaptureEvent method but it still doesn't seem to work--or at least onCaptureEvent is unable to find the updated state value.
Is there a way to ensure