React-native-ble-plx: Bug Report userState React Hooks

Created on 19 Jan 2020  路  24Comments  路  Source: Polidea/react-native-ble-plx

Expected Behavior

When using "const [devices, setDevices] = useState([])" or any statefunction inside the scaning from 'startDeviceScan' the event stops

Current Behavior

The event stop and no more devices are found

Context

[CoreBluetooth] API MISUSE: has no restore identifier but the delegate implements the centralManager:willRestoreState: method. Restoring will not be supported
2020-01-19 20:06:06.293648+0100 Zipforce[23222:18432691] [CoreBluetooth] XPC connection invalid

  • Library version: 1.1.1
  • Platform: OS.
  • Platform logs (XCode):
const bleManager = new BleManager();

    const [deviceScan, setDeviceScan] = useState(false)
    const [devices, setDevices] = useState([])

    const stopDeviceScan = () => {
        console.trace('stopDeviceScan')
        bleManager.stopDeviceScan()
        setDeviceScan(false)
    }

    const addDevice = (device) => {
        console.trace('addDevice')
        console.log(device.id, device.name, device.localName)
        if (device.isConnectable &&
            device.localName &&
            devices.findIndex((x) => x.id == device.id) === -1) {

            setDevices([...devices, {
                id: device.id,
                name: device.name,
                localName: device.localName,
                isConnectable: device.isConnectable,
                rssi: device.rssi
            }])
        }
    }

    const startDeviceScan = () => {
        console.trace('startDeviceScan')
        bleManager.startDeviceScan(null, { allowDuplicates: false }, (error, device) => {
            if (error) {
                console.error(error)
            } else {
                addDevice(device)
            }
        })
    }

    useEffect(() => {
        console.trace('Init - Timer')
        let timerId = setTimeout(() => {
            stopDeviceScan()
        }, 15000)
        return () => clearTimeout(timerId);
    }, [])

    useEffect(() => {
        console.trace('Init')
        setDevices([])
        setDeviceScan(true)
        const subscription = bleManager.onStateChange((state) => {
            switch (state) {
                case 'PoweredOn':
                    console.trace('PoweredOn')
                    subscription.remove();
                    startDeviceScan()
                    break;

                default:
                    break;
            }
        })
    }, [])

    useEffect(() =>{
        console.log('useEffect',devices)
    },[devices])
bug stale

Most helpful comment

I've noticed the same thing when using monitorCharacteristicForService(). If I try to update state inside of my listener function, it only runs once and then stops.

All 24 comments

I've noticed the same thing when using monitorCharacteristicForService(). If I try to update state inside of my listener function, it only runs once and then stops.

Can you send me more logs from the native side?

Also experiencing this, has anyone found a workaround?

For us the stream of characteristics from Characteristic.monitor is interrupted when useState is called

We found that this is because the device is being disconnected on the rerender but not got a solution yet

(By the way the library is great)

I am expierencing the same thing! Whenever i use setState, i need to reconnect...

The issue does not occur if you move the search function outside of the main function

@senner007 Thanks for the quick reply! But how exactly do you mean that? Outside of the Component?

@Krister-Johansson I think your expected behavior description is incorrect.

@senner007 Thanks for the quick reply! But how exactly do you mean that? Outside of the Component?

Extract the search function and pass in a function to update the state

@senner007

But isnt this exactly whats happening here:

      const startDeviceScan = () => {
        console.trace('startDeviceScan')
        bleManager.startDeviceScan(null, { allowDuplicates: false }, (error, device) => {
            if (error) {
                console.error(error)
            } else {
                addDevice(device)
            }
        })
    }

Because you scan, and call another function with the device to save it to the state

Yes, it is the same and it also works for me. I can't reproduce the issue then....

This seems to be the same issue here i am facing:

https://github.com/Polidea/react-native-ble-plx/issues/627

I dont see that the scanning stops, but I do see that this:

  setDevices([...devices, {
                id: device.id,
                name: device.name,
                localName: device.localName,
                isConnectable: device.isConnectable,
                rssi: device.rssi
            }])

needs to be this :

  setDevices(prevDevices => [...prevDevices, {
                id: device.id,
                name: device.name,
                localName: device.localName,
                isConnectable: device.isConnectable,
                rssi: device.rssi
            }])

hmmm i tried this.. but this does not resolve the issue for me... really weird that i have this problem only with useState... if i use a Class-Component and not Functional-Component with setState i dont have this issue at all!

I keep getting the error "Device is not connected" with the right DeviceID

Just to make things clear. You shouldn't create BleManager inside the component. Is that true in your case?

@Cierpliwy Wow! Just tried it! Fixed my problem! Damn... that was really stupid of me!

I ve been trying to fix this for at least 2 Days now...

But how come this works inside a Class-Component:

import React, { Component } from 'react';
import { Platform, View, Text } from 'react-native';
import { BleManager } from 'react-native-ble-plx';

export default class SensorsComponent extends Component {

  constructor() {
    super()
    this.manager = new BleManager()
    this.state = {info: "", values: {}}
    this.prefixUUID = "f000aa"
    this.suffixUUID = "-0451-4000-b000-000000000000"
    this.sensors = {
      0: "Temperature",
      1: "Accelerometer",
      2: "Humidity",
      3: "Magnetometer",
      4: "Barometer",
      5: "Gyroscope"
    }
  }
.....

Taken from https://www.polidea.com/blog/ReactNative_and_Bluetooth_to_An_Other_level/

There are problems with how hooks are used in the initial code. This has to do with closures.
devices array will always be empty in addDevice()

Yeah, example was prepared to be concise. In general I highly recommend to create clear separation between BLE logic and UI of your application.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@Cierpliwy Could you explain why the BleManager cannot be instantiated inside the component?

Also, it would be extremely helpful to have an example react-native-ble-plx implementation using functional components. Do you know if this exists?

It can be instantiated using functional approach. It's just that the first example has errors. See what I wrote about the closure issue

i believe the problem arrises from the state hook causing a re-render.
i fixed it by useRef for the manager and useReducer, rather then useState for storing the devices i get back from the scan.

i believe the problem arrises from the state hook causing a re-render.
i fixed it by useRef for the manager and useReducer, rather then useState for storing the devices i get back from the scan.

Could you please post your code?

i believe the problem arrises from the state hook causing a re-render.
i fixed it by useRef for the manager and useReducer, rather then useState for storing the devices i get back from the scan.

Could you please post your code?

i've moved all of this into a context manager but i can post a few snippets diggin into the IED history ;)
they probably won't work as is, but it'll get you going in the right direction

setup a reducer and actions to dispatch:

const initialState = [];
const  reducer = (state, action) =>  {
  switch (action.type) {
  case 'add':
    return [
      ...state,
      {
        id: action.device.id,
        name: action.device.name,
        device: action.device
      }];
  case 'set':
    return action.devices;
  case 'clear':
    return initialState;
  default:
    return state;
  }
}
  const clear = () => ({type: 'clear'});

  const addDevice = (device) => ({
    type: 'add',
    device
  });

setup the useReducer and useRef hooks:

const [devices, dispatch] = useReducer(reducer, initialState);
const manager = useRef(new BleManager());

intialize the bleManager and start a scan:

  const scan = () => {
    manager.current.startDeviceScan(null, null, (error, device) => {
      if (error) {
        console.log(error);
        return;
      }
      console.log(device.name);
      dispatch(addBLE(device));
    });
  };

  const startScan = () => {
    dispatch(clear());
    const subscription = manager.current.onStateChange((state) => {
      if (state === 'PoweredOn') {
        scan();
      }
    }, true);
  };

call startScan with a useEffect or a button, that'll start the scan, to see the data, loop through devices

{devices.map((device, index) => {
   console.log(device.id)
})}

hope this helps

You need to place your manager = new Manager() code outside of the function or class. Otherwise react renders a new manager on every state change.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

brycejacobs picture brycejacobs  路  5Comments

alfacommunication-alessandro picture alfacommunication-alessandro  路  4Comments

paulclark94 picture paulclark94  路  3Comments

SlavaInstinctools picture SlavaInstinctools  路  4Comments

nriccar picture nriccar  路  3Comments