Scanner API state does not reset

Hello,

We’ve just launched a POS extension for verifying U.S. drivers licenses. We’re making use of the Barcode Scanner API to capture details from the ID.

However, we’ve noticed an issue with how the Scanner API works, and we’re not sure how to work around it.

The issue

The scanner API only provides a hook to listen to the scanner’s current data.

The problem we have is that there doesn’t seem to be a way to reset the scanner’s data, even if you unmount and remount a dedicated scanning component.

Example

Here’s an naive example. We unmount and remount the <Scanner /> component after the merchant scans a barcode successfully and clicks “Scan again”, but in practice the useScannerDataSubscription()'s data variable will not reset state.

Therefore the useEffect on the data will fire immediately.

import React, { useState } from 'react';
import {
  Navigator,
  Screen,
  Stack,
  Text,
  useScannerDataSubscription,
  reactExtension,
  Scanner,
} from '@shopify/ui-extensions-react/point-of-sale';

const SmartGridModal = () => {
  const [state, setState] = useState('ready');
  const {data, source} = useScannerDataSubscription();

  return (
    <Navigator>
      <Screen name="Home" title="Home">
         { state === 'ready' && <Scanner setData={setData} setState={setState} /> }
         { state === 'scanned' && <Done data={data} setState={setState} /> }

      </Screen>
    </Navigator>
  );
};

const Scanner = ({ setData, setState }) => {
  useEffect(() => {
    setState('done');
    setData(data);
   }, [data])

  return (
    <Scanner />
  );
}

const Done = ({data, setState}) => {
  return (
        <Stack direction="horizontal">
          <Text>{`Scanned data: ${data}`</Text>
          <Button onPress={() => { setState('ready'} }>
             Scan again
          <Button>
        </Stack>
  )
}

export default reactExtension('pos.home.modal.render', () => (
  <SmartGridModal />
));

Hi @Dylan, thanks for the feedback. Unfortunately, this is due to the nature of subscribables, and the way they are held onto in memory on the POS side so long as the extension is running. I see this is a bad API design for your use case, so I think what we can look into is providing a reset() function as a part of the object returned by the useScannerDataSubscription hook. I’ll create a ticket in our backlog to try and get this in for a future release.

2 Likes

@JS_Goupil thanks for the note here!

Any idea of a workaround for the time being?

@Dylan if you figure out a workaround please share, and good luck in the meantime!!

1 Like

@RobDukarski I don’t think there’s a work around for completely resetting the state of the data, if getting it to be undefined is what you’re trying to accomplish. Can you provide a specific example of what you’re trying to achieve on a higher level, and I’ll see if I can think of an approach?

@JS_Goupil For us, the biggest pain point is that merchants have to close the tile and re-open it to scan multiple barcodes. Since that’s the only way to clear the state of the data variable.

We’ve tried using a ref in combination with the data variable from the hook, but still not able to find a workaround to re-load the Camera without the useEffect on the data variable firing immediately.

@Dylan , interesting, I would have thought that you can still scan multiple items serially without closing the modal. ie The scanner should replace the current value with the new value. You’re saying that’s not the case?

@JS_Goupil You can, but the problem is writing a useEffect that can differentiate between scans, since you can only depend on the data attribute.

In our case we need to make an external API call to process the information on the barcode.

So we need a loading state, then a results state.

There’s quite a bit of data extracted, so we need to minimize/unmount the camera component while viewing results.

Then when you attempt to remount the Scan component, since data is populated from the first scan, it’ll fire the useEffect statement process the old data, and then you’re looping the customer back to the Results state before they have the opportunity to scan a different barcode.

// Scanner.js example

const { data } = useScannerDataSubscription();

useEffect(() => {
  if(!data) return;

  // first effect call data == null -> data == "first scan"
  // second effect call data === "first scan", so someApiRequest it fires immediately again

  someApiRequest
    .then(() => { setState('processed') }
}, [data])

This may not be helpful but would it be possible to add some sort of unique event identifier to the data being sent each call so that if it receives the same identifier again it will know to ignore it and you can proceed like you want?

Just trying to help think of a workaround for the time being…

@RobDukarski yes potentially. I think there’s still some complications around rescanning the same data again in case of a network outage/API outage.

The merchant wouldn’t be able to rescan because the data would effectively been the same twice.

The identifier couldn’t rely on the barcode data alone, it’d need to be a combination of the data + some kind of debouncing timestamp I guess.