Unexpected behavior when scanning with TC21
Closed this issue · 22 comments
Hi everyone. I'm experiencing some strange behavior with a flatist, or at least I can't explain it. I've implemented the useEffect hook to start and stop scanning. When a barcode is scanned, I add an element to a state object, and consequently my flatlist is updated. I also implemented a "swipe to delete" strategy. Suppose I added five scanned list items to the flatlist and deleted two of them. I then scan a new barcode, but the flatlist shows all the items, even the deleted ones. Why? How can I fix this?
Thanks in advance
Damian.
const [listOfBultos, setListOfBultos] = useState([]);
useEffect(() => {
const event = ExpoZebraScanner.addListener(e => {
const {scanData, scanLabelType} = e;
handleZebraScanned(scanData);
});
ExpoZebraScanner.startScan();
return () => {
ExpoZebraScanner.stopScan();
event.remove(); //ExpoZebraScanner.removeListener(event);
}
},[]);
Hi, can you provide all the code for the component? The bug might be coming from your function to update or the equality render from the FlatList.
package.json
Yes, of course. In this release I uploading, you can see that I'm moved the code from useEffect hook to the useFocuseffect hook for testing purposes, but the problem persists.
Archivo.zip
It seems that with the useFocusEffect the startScan and stopScan methods are fired accordingly when navigating between the bottom tabs. Unlike useEffect, that did not.
Thank you. Please, let me know if you need something else
Regards
Well, further testing demonstrate I'm wrong. Using useFocusEffect did not works. Here the explanation:
LOG Transferencia.js ExpoZebraScanner startScan
LOG Recepcion.js ExpoZebraScanner startScan
LOG Transferencia.js ExpoZebraScanner stopScan
LOG Recepcion.js ExpoZebraScanner stopScan
ERROR Error: Call to function 'ExpoZebraScanner.stopScan' has been rejected.
→ Caused by: java.lang.IllegalArgumentException: Receiver not registered: expo.modules.zebrascanner.BarcodeReceiver@945ceac
Second startScan is invoked before the stopScan. So now, I'm moving the code back to the useEffect hook.
Regards
Damian.
Ok, moving code back again to useFocusEffect hook, and catching the previous error seems to work fine. Now, another strange behavior: scanning once but, instead of one item, two or three items are added to the flatlist
Before I read the code (I'm not available now), can you just add a console.log(scanData)
to your handleZebraScanned
?
If the console.log always returns true data, it's certain that the bug is not in this library but in your swipe-to-delete functionality or in the implementation of your FlatList.
In react native you do not need that code, after you install module every textInput have ability to scann.
The Zebra terminal can fill Text Input, but if you don't want to focus on the text input or not use it, you need to use the useEffect hook.
The issue is not with the library; the problem lies in the code errors. You need to use the callbacks of setState to always have the updated value as shown below. Additionally, you cannot modify a React state directly with unshift; you should use the spread operator ([...arr]) before editing the variable.
From:
const agregarBulto = (registro) => {
const max = 1000000;
const id = Math.floor(Math.random() * max);
const bulto = {id: id, articulo_cod: registro.articulo_cod, cantidad: registro.cantidad, precio_unitario: registro.precio_unitario};
if(!bulto) {
return;
}
if(!bulto.articulo_cod) {
return;
}
if(bulto.articulo_cod === "") {
return;
}
let lista = listOfBultos;
lista.unshift(bulto); // <== You can't do that
setListOfBultos([...lista]);
}
To:
const agregarBulto = (registro) => {
const max = 1000000;
const id = Math.floor(Math.random() * max);
const bulto = {id: id, articulo_cod: registro.articulo_cod, cantidad: registro.cantidad, precio_unitario: registro.precio_unitario};
if(!bulto) {
return;
}
if(!bulto.articulo_cod) {
return;
}
if(bulto.articulo_cod === "") {
return;
}
setListOfBultos((currVal) =>{
let newArr = [...currVal];
newArr.unshift(bulto);
return newArr
});
}
Hi dragoslavIvkovic, aclec. Thank hoy both for your answers.
You mean I don't need handleZebraScanned?
Aclec, the code you kindly suggest before makes works better, but still adds more than one item to the flatlist. I'm new to react native, I don't understand what you mentioned about why my code is wrong. if it's not too much to ask, can you explain it further?
If you click on the text input (to select it) and scan with the zebra, the zebra will fill the text input with the scanned text.
It's not a React Native problem, it's a React problem.
When you write: let lista = listOfBultos;
=> You create a reference to the array, and the unshift method edits the reference. This is prohibited in React because it mutates the state directly (that's why you use setState).
You can use the callback update in setState: setState(currValue => [...currValue])
.
Or you can use the spread operator before:
let newArr = [...arr];
newArr.push("test"); // Updates a new array because with the spread operator, it's not a reference to the React state
setState(newArr);
Take a look at the React documentation for more details.
For the code, try to use:
setListOfBultos((currVal) =>{
return [bulto, ...currVal];
});
Thanks a lot, again. It seems like the problem arises when navigating between bottom tabs. May be an issue with stopScan and startScan?
I have a useEffect on each of my bottom tabs and it work perfectly.
I give you an exemple of my component to handle zebra scan:
import React, {Dispatch, SetStateAction, useEffect} from "react";
import {Platform} from "react-native";
import * as ExpoZebraScanner from "expo-zebra-scanner";
import {useRecoilValue} from "recoil";
import {_paloxNumbersScanned} from "../../store/palox/_paloxNumbersScanned";
import {_employee} from "../../../staff/store/_storeEmployee";
interface Props {
setListEmployee: Dispatch<SetStateAction<string[]>>
setListNumber: Dispatch<SetStateAction<string[]>>
}
export default function PaloxScanZebra({setListEmployee, setListNumber}: Props){
const staffEmployee = useRecoilValue(_employee);
const paloxNumbersScanned = useRecoilValue(_paloxNumbersScanned);
// --- Effects ---
useEffect(() => {
if(Platform.OS === "android"){
const listener = ExpoZebraScanner.addListener(event => {
const { scanData } = event;
const handleAddNumber = () => {
setListNumber(currentList => {
if(currentList.includes(scanData) || paloxNumbersScanned.includes(scanData)){
return [...currentList]
}else {
return [scanData, ...currentList]
}
});
}
try {
// Load data and check if it's Transfer QR
const dataLoaded = JSON.parse(scanData);
const keys = Object.keys(dataLoaded);
// Check needed keys are here
if( keys.includes("app") && keys.includes("myId") && keys.includes("type") ){
if( (dataLoaded.app === "xxxx" || dataLoaded.app === "xxxx") && dataLoaded.type === "employee" ){
setListEmployee(currentList => {
if(!currentList.includes(dataLoaded.myId) && staffEmployee.includes(dataLoaded.myId)){
return [dataLoaded.myId, ...currentList]
}else {
return [...currentList]
}
})
}
}else {
handleAddNumber();
}
}catch (e) {
handleAddNumber();
}
});
ExpoZebraScanner.startScan();
return () => {
ExpoZebraScanner.stopScan();
listener.remove();
}
}
})
return(
<></>
)
}
And with flat list in both bottom tabs? Can't make it work. Items added in bottom tab number 1 are showed in bottom tab number 2, and viceversa...
And with flat list in both bottom tabs? Can't make it work. Items added in bottom tab number 1 are showed in bottom tab number 2, and viceversa...
If you want to share between screens use global state.
If you don't want, use local state in each of screen components.
Put your zebra scranner in each of bottom tab component.
With "global state" you mean that I need just once state for both screens? So, it's the normal behavior that "Items added in bottom tab number 1 are showed in bottom tab number 2, and viceversa"?
If your lists share the same state as the source, it's normal for them to show the same data. To have two different lists, you need two different states.
If you already have two states but it still renders the same list, there is another error in your code (e.g., refresh, props, or something else).
Yes, I've two different states. In fact, there are two different screens: Transferencia.js and Recepcion.js
But they share the component BultosList that renders the flatlist.
Thanks again. I'll still looking for the issue here...
I have reviewed your code and found some inconsistencies with your use of setState.
Please rewrite these logics to ensure immutable values (use the spread operator before modifying the array).
Additionally, reduce your components to approximately one hundred lines for better readability.
Thank you aclec. I'm working on it. Let me ask something else. I want to let the user sort the flatlist. From your previous comments, I must use spread operator also, right? Something like this:
let newArray = [...listOfBultos].sort((a, b) => (a.items - b.items));
setListOfBultos(newArray);
Yes, it’s like that