"Synchronous" useState without useEffect

Viewed 235

I have a list of detected Bluetooth devices, deviceList, that is setup with useState:

const [deviceList, setDeviceList] = useState([]);

I have a button that initiates device scans. I need the device list to be reset to [] every time a scan starts. I do:

const scanForDevices = () =>  {
    setDeviceList([]);

    manager.startDeviceScan(null, null, (error, device) => {
        // add device to device list
        setDeviceList(old => [...old, device]);
    }
}

The problem is that setDeviceList is not synchronous, and deviceList is not reset by the time the new device is supposed to be added. This can result in duplicate devices. I can prevent duplicates by filtering, but that's just a hack on the actual problem.

I cannot use useEffect to solve this problem so please do not suggest it. startDeviceScan cannot be run every time deviceList changes.

How are situations like this solved in the absence of truly atomic setState?

2 Answers

If you're trying to clear the list and startDeviceScan is returning multiple devices, it seems like you could create a temporary variable to build the list and then set the state when you're finished.

const scanForDevices = () =>  {
    const devices = [];

    manager.startDeviceScan(null, null, (error, device) => {
        // add device to device list
        devices.push(device);
    }

    setDeviceList(devices);
}

Have you tried using the useReducer hook to change your state? I haven't tried to solve a timing issue like yours with this hook, but it's worth a shot.

The solution might look something like this (untested):

const initialState = []
const reducer = (state, {type, payload}) => {
  switch (type) {
    case 'RESET':
      return initialState
    case 'ADD_DEVICE':
      return [...state, payload]
    default:
      return state
  }
}
const [deviceList, dispatch] = useReducer(reducer, initialState)

...

const scanForDevices = () =>  {
  dispatch({type: 'RESET'})

  manager.startDeviceScan(null, null, (error, device) => {
    // add device to device list
    dispatch({type: 'ADD_DEVICE', payload: device})
  }
}
Related