Current code:
import QRCodeScanner from 'react-native-qrcode-scanner';
function ScanScreen({ navigation }) {
return (
<SafeAreaView style={styles.screen}>
<QRCodeScanner reactivate={true} reactivateTimeout={3000}
onRead={data => navigation.navigate('Third', {target:data.data})}
/>
</SafeAreaView>
);
}
It works, but here's what I want to do:
The user can navigate between screens, one of them being a QR code scanner.
While scanning, I need to debounce the scanner so it doesn't keep generating onRead events. The user could
a) start a scan in the scan screen without reading a QR code and navigate manually to another screen.
b) read a QR and automatically move to another screen for processing, then go back to scan again.
For this reason I need to re-enable the scanner after some reasonable time.
I can't just set reactivate to false because then the QR scanner is inactive until I restart the app.
The problem is that when the user stays in Another screen, the QR scanner re-activates after the timeout and tries to scan when it is not desired. What I ideally would like to do is to deactivate the QR scanner while the user is not in the scan screen, and re-activate it with the above mentioned parameters whenever the user enters the scan screen.
Is there any way to do this? Thanks!
I had almost the same problem. The scanner does not stop scanning while showing another View (using reactivate={true}). I am using react-navigation and so I came up to following solution.
You can listen on what is happening with your view with focus and blur.
this.props.navigation.addListener('focus', () => {
this.setState({viewFocused: true});
});
this.props.navigation.addListener('blur', () => {
this.setState({viewFocused: false});
});
Note: you place this piece of code in componentDidMount or using React.useEffect.
Based on the state viewFocused you can render the QR Code Scanner.
this.state.viewFocused && (
<QRCodeScanner
onRead={onRead}
reactivate={true}
reactivateTimeout={2000}
/> );
This helps me to solve my problem. To not scan while showing other views but to scan if the view is shown again. Credits to
pkyeck on github.com
as explained in the doc, you can do it progammatically
<QRCodeScanner
onRead={this.onSuccess}
ref={(node) => { this.scanner = node }} <-- add this
/>
and in your method (for example, in Alert or onPress button), reactivate the scanner like this:
onPress: () => {
this.scanner.reactivate()
},
With React hooks:
let scanner = useRef(null);
<QRCodeScanner
onRead={onSuccess}
ref={node => { scanner = node;}}
/>
Then you can attach it to a button. For example:
onPress={() => scanner.reactivate()}
Alternatively, Re-rendering screen with the useIsFocused hook.
React Navigation useIsFocused
import {useIsFocused} from '#react-navigation/native';
const ScannerCamera = ({navigation, route}) => {
const viewFocused = useIsFocused();
return (
viewFocused && (
<QRCodeScanner
containerStyle={{height: SCREEN_HEIGHT}}
cameraStyle={[{height: SCREEN_HEIGHT}]}
showMarker={true}
onRead={onSuccess}
flashMode={RNCamera.Constants.FlashMode.auto}
/>
)
);
};
Same thing as mentioned above in functional component or using hooks
import React, {useEffect, useState} from 'react';
const ScannerCamera = ({navigation, route}) => {
let uuId = null;
const [viewFocused, setViewFocused] = useState(false);
useEffect(() => {
const onFocus = navigation.addListener('focus', () => {
setViewFocused(true);
});
const onBlur = navigation.addListener('blur', () => {
setViewFocused(false);
});
return {onFocus, onBlur};
}, [navigation]);
return (
viewFocused && (
<QRCodeScanner
containerStyle={{height: SCREEN_HEIGHT}}
cameraStyle={[{height: SCREEN_HEIGHT}]}
showMarker={true}
onRead={onSuccess}
flashMode={RNCamera.Constants.FlashMode.auto}
/>
)
);
};
Related
I created a barcode scanner App using expo-barcode-scanner.
I have some problems.
The purpose of the scanner is to get the barcode number and send it to barcode.monster and get product details. It works, but I have two main problems which I dont know what should I look for and how to resolve.
After the scanner get a barcode, I want to send to a confirmation screen, where the User should add the product into a category.
const handleBarCodeScanned = ({ type, data }) => {
reqForProduct(data);
setScanned(true);
setText(data);
navigation.navigate('Confirmation');
};
The function above is executed when the barcode camera find a number.
const reqForProduct = async barcode => {
try {
const Product = await axios.get(`https://barcode.monster/api/${barcode}`);
console.log(Product.data);
} catch (error) {
console.log(error);
}
}
The function above is responsible to get the product data.
THE NAVIGATION WORKS, BUT IF I PRESS THE BACK BUTTON AFTER THE FUNCTION SEND ME TO THE CONFIRMATION SCREEN, I CANNOT RESCAN OTHER BARCODE UNLESS I PRESS R (RELOAD) IN THE CONSOLE... THIS IS MY FIRST PROBLEM. Moreover, after coming back to the screen, the console is stucked with the last product fetched from the api.
The second problem is is to transfer the data fetched to the confirmation screen. I tried with the navigation prop like navigation.navigate('Confirmation', {fetchedDataObj} but is not working....
<Stack.Screen
name='Confirmation'
component={AddToContainerScreen} />
THE FULL PAGE CODE BELLOW ----------------------------------------------------
import {View, Text, Button, StyleSheet} from 'react-native';
import {useState, useEffect} from 'react';
import { BarCodeScanner } from 'expo-barcode-scanner';
import axios from 'axios';
const Scanner = ({navigation}) => {
const [permission, setPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [text, setText] = useState('');
const permissionCamera = () => {
( async () => {
const {status} = await BarCodeScanner.requestPermissionsAsync();
setPermission(status == 'granted');
})()
}
const reqForProduct = async barcode => {
try {
const Product = await axios.get(`https://barcode.monster/api/${barcode}`);
console.log(Product.data);
} catch (error) {
console.log(error);
}
}
// Execute permission
useEffect(() => {
permissionCamera();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
reqForProduct(data);
setScanned(true);
setText(data);
navigation.navigate('Confirmation');
};
if (!permission) {
return (
<View>
<Text>Requesting camera permission</Text>
</View>
)
}
return (
<View style={styles.wrapper}>
<BarCodeScanner
style={StyleSheet.absoluteFill}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
/>
</View>
)
};
const styles = StyleSheet.create({
wrapper: {
flex: 1
}
})
export default Scanner;
Can someone please help me?
BTW THE PRODUCT DATA FROM tHE API COMES SLOWeR THAN the APP MOVES TO THE CONFIRMATION SCREEN...
Problem 1: I think you need to reinitialize it on a focus even listener or something.
useEffect(() => {
permissionCamera();
}, []);
since useEffect() is basically a componentOnMount and it only fires the first time you load the page. When you navigate back this is not gonna fire. Please check if this is the case. You can do a simple log to confirm this.
For the 2nd problem, I can't help you much since there is only very little data. If you really need help, you could dm me on skype. I'll be glad to help you out.
i have two button and they call same function like this:
this button created in useEffect.
useEffect(() => {
navigation.setParams({
TopRightButton: (
<ConfirmButton
callback={() => ApiCall()}
/>
),
});
}, []);
this is second button in render
<TouchableOpacity onPress={()=> ApiCall()}><Text>Add</Text></TouchableOpacity>
this is function:
const ApiCall = () => {
console.log('name', Name);
};
i change name with useState. when i click TouchableOpacity which is second button, it shows me name. However, when i click first button which is created in useEffect, nothing happen. i mean, Name is null. why this is happen ?
any advice ?
EDITED:
const [Name, setName] = useState('');
Will u try this way?
const ApiCall = () => {
console.log('name', Name);
};
useEffect(() => {
navigation.setParams({
TopRightButton: (
<ConfirmButton
callback={() => ApiCall()}
/>
),
});
}, [Name]);
You should provide more information concerning that first button, im guessing it is a button that you want to show on the right side of your stack navigator's header, if that's the case you should NOT use setParams for that.
If you want to configure your navigator you should use navigationOptions.
So in the screen where you want to add a button in its header, you have to call setOptions inside useEffect instead. Check out the docs for more info on how to achieve that.
I am trying to navigate to a certain screen on my bottom-tab-navigator when a user opens the app by clicking a notification.
Looking into the official docs Navigating without the navigation prop, my setup of my main navigator is as follows:
import {navigationRef, isReadyRef} from './root';
const MainNav = _ => {
if (isLoading) {
return isFirstTime ? (<OnBoarding />) : (<SplashScreen />);
}
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {isReadyRef.current = true}}>
{!token ? <AuthNav /> : <AppNav />}
</NavigationContainer>
);
}
My root.js is as follows:
import * as React from 'react';
export const isReadyRef = React.createRef();
export const navigationRef = React.createRef();
export function navigate(name, params) {
if (isReadyRef.current && navigationRef.current) {
// Perform navigation if the app has mounted
navigationRef.current.navigate(name, params);
} else {
// You can decide what to do if the app hasn't mounted
// You can ignore this, or add these actions to a queue you can call later
console.log('Not mounted yet.')
}
}
And I had added the OneSignal event listener in my root index.js as following:
const App = _ => {
useEffect(() => {
OneSignal.addEventListener('opened', onOpened);
return () => OneSignal.removeEventListener('opened', onOpened);
}, []);
return {
<StoreProvider store={store}>
<MainNav />
</StoreProvider>
}
}
And my onOpened function is as follows:
import {navigate} from '../nav/root';
const onOpened = ({notification}) => {
if(notification.type == 'New Request'){
navigate('Notifications');
}
}
But when I test it as expected Not mounted yet. is printed to console. So I want to
add these actions to a queue you can call later
as stated by the official react navigation docs but I am not sure how to do this. I found react-native-queue but it is no longer being maintained and using a setTimeout just seems like an ugly hack cause the load time varies. So is there a better approach or solution that I can use to navigate only after the loading is done (I am thinking of using redux for this) and my navigators have been mounted (not sure how to do this)?
I am using a Dialog component from 'react-native-paper' in a simple functional component like:
...
const myScreen = () => {
useEffect(() => {
Keyboard.addListener('keyboardDidShow', frames => {
// get the Dialog component measures
});
);
return (
<View>
...
<Portal>
<Dialog>
...
</Dialog>
</Portal>
</View>
);
}
I need to get the size and position of the dialog on screen in the useEffect.
I have tried every suggested solution and nothing seems to work.
Perhaps what I think can solve my issue is not the right one. Happy to hearing ideas. I am getting:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and async task in a useEffect cleanup function
and tracked it down to one component that is in my headerRight portion of the status bar. I was under the impression it mounts only once. Regardless, the component talks to a syncing process that happens and updates the state. For each status of the sycing, a different icon is displayed.
dataOperations is a NativeModules class that talks to some JAVA that does the background syncing and sends the status to RN.
import React, {useState, useEffect} from 'react';
import {DeviceEventEmitter } from 'react-native';
import DataOperations from "../../../../lib/databaseOperations"
const CommStatus: () => React$Node = () => {
let [status, updateStatus] = useState('');
const db = new DataOperations();
const onCommStatus = (event) => {
status = event['status'];
updateStatus(status);
};
const startSyncing = () => {
db.startSyncing();
};
const listner = DeviceEventEmitter.addListener(
'syncStatusChanged',
onCommStatus,
);
//NOT SURE THIS AS AN EFFECT
const removeListner = () =>{
DeviceEventEmitter.removeListener(listner)
}
//REMOVING THIS useEffect hides the error
useEffect(() => {
startSyncing();
return ()=>removeListner(); // just added this to try
}, []);
//TODO: find icons for stopped and idle. And perhaps animate BUSY?
const renderIcon = (status) => {
//STOPPED and IDLE are same here.
if (status == 'BUSY') {
return (
<Icon
name="trending-down"
/>
);
} else if (status == 'IS_CONNECTING') {
...another icon
}
};
renderIcon();
return <>{renderIcon(status)}</>;
};
export default CommStatus;
The component is loaded as part of the stack navigation as follows:
headerRight: () => (
<>
<CommStatus/>
</>
),
you can use App.js for that.
<Provider store={store}>
<ParentView>
<View style={{ flex: 1 }}>
<AppNavigator />
<AppToast />
</View>
</ParentView>
</Provider>
so in this case will mount only once.