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.
Related
I can't even tell you how many variations I have tried, tutorials and documentations that I have watched and read, I cannot transfer data from one page to another. Am using react native expo. I have this included in both pages: import AsyncStorage from '#react-native-async-storage/async-storage';.
This is the page I'm trying to set the data:
const ToyDetails = () => {
const [savedName, setSavedName] = useState('')
const addCart = async() => {
setButtonText('ADDED TO CART!')
try {
await AsyncStorage.setItem('saved_name', savedName)
}catch(error){
console.log(error)
}
}
return(
<View>
<Text value={savedName}>{name}</Text>
#{name} is because I am importing the name from a FlatList item
</View>
)
}
And getting that data from another page:
const Cart = () => {
const [savedName, setSavedName] = useState('')
useEffect(()=>{
getData()
}, [])
const getData = () => {
try {
AsyncStorage.getItem('saved_name')
.then((value)=>{
if(value!=null){
setSavedName(value)
}
})
}catch(error){
console.log(error)
}
}
return (
<View>
<Text value={savedName} onChangeText={(value)=>setSavedName(value)}>{savedName}</Text>
</View>
)
}
I can post other variations I have tried if asked, I've tried adding it into a list and importing the list in the second page, I've tried to JSON.stringify the value savedName first (and JSON.parse it), I even tried doing it in the same way I did for FlatList. I'm not even getting any error messages.
in your ToyDetails.js while saving savedName is empty. i changed to name and able to get it on CartScreen
https://snack.expo.dev/7ozHrsOBT check ToyDetails.j file
const addCart = async() => {
setButtonText('ADDED TO CART!')
try {
console.log("savedName",savedName) //saved name is empty here
await AsyncStorage.setItem('saved_name', name)
}catch(error){
console.log('setitem didnt work')
}
}
I'm developing a mobile app with React Native and Expo managed workflow. The app is supposed to serve as a song book with lyrics to songs and hymns. All of the lyrics are stored in Firebase' Firestore database and clients can load them in app. I started to implement offline functionality, where all of the lyrics are stored on the user's device using community's AsyncStorage.
I want to get the data stored in AsyncStorage first, set them to state variable holding songs and then look if user has internet access. If yes, I want to check for updates in Firestore, if there were any, I will set the data from Firestore to state variable holding songs. If user does not have internet access, the data from AsyncStorage will already be set to state variable holding songs.
I'm trying to achieve this with an async function inside useEffect hook with empty array of vars/dependecies. The problem I'm having is that no songs are rendered on screen even though they are successfuly retrieved from AsyncStorage.
(When I console.log the output of retrieving the data from AsyncStorage I can see all songs, when I console log songs or allSongs state var, I'm getting undefined)
Here is my simplified code:
import React, { useEffect, useState } from 'react';
import {
StyleSheet,
FlatList,
SafeAreaView,
LogBox,
View,
Text,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { filter, _ } from 'lodash';
import { doc, getDoc } from 'firebase/firestore';
import NetInfo from '#react-native-community/netinfo';
import { db } from '../../../firebase-config';
import { ThemeContext } from '../../util/ThemeManager';
import {
getStoredData,
getStoredObjectData,
storeData,
storeObjectData,
} from '../../util/LocalStorage';
const SongsList = ({ route, navigation }) => {
// const allSongs = props.route.params.data;
const { theme } = React.useContext(ThemeContext);
const [loading, setLoading] = useState(false);
const [allSongs, setAllSongs] = useState();
const [songs, setSongs] = useState(allSongs);
const hymnsRef = doc(db, 'index/hymns');
useEffect(() => {
const setup = async () => {
setLoading(true);
const locData = await getStoredObjectData('hymnsData');
console.log(locData);
setAllSongs(locData);
setSongs(locData);
const netInfo = await NetInfo.fetch();
if (netInfo.isInternetReachable) {
const data = await getDoc(hymnsRef);
const lastChangeDb = data.get('lastChange').valueOf();
const hymnsData = data.get('all');
const lastChangeLocal = await getStoredData('lastChange');
if (lastChangeLocal) {
if (lastChangeLocal !== lastChangeDb) {
await storeData('lastChange', lastChangeDb);
await storeObjectData('hymnsData', hymnsData);
setAllSongs(hymnsData);
setSongs(hymnsData);
}
}
}
sortHymns();
setLoading(false);
};
setup();
}, []);
return (
<SafeAreaView style={[styles.container, styles[`container${theme}`]]}>
{!loading ? (
<FlatList
data={songs}
keyExtractor={(item) => item?.number}
renderItem={({ item }) => {
return <ListItem item={item} onPress={() => goToSong(item)} />;
}}
ItemSeparatorComponent={Separator}
ListHeaderComponent={
route.params.filters ? (
<SearchFilterBar
filters={filters}
handleFilter={handleFilter}
query={query}
handleSearch={handleSearch}
seasonQuery={seasonQuery}
setSeasonQuery={setSeasonQuery}
/>
) : (
<SearchBar handleSearch={handleSearch} query={query} />
)
}
/>
) : (
<View>
<Text>loading</Text>
</View>
)}
<StatusBar style={theme === 'dark' ? 'light' : 'dark'} />
</SafeAreaView>
);
};
export default SongsList;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The functions getStoredData, getStoredObjectData, storeData and storeObjectData are just getItem and setItem methods of AsyncStorage.
Here is my full code - GitHub.
What am I doing wrong? I've went over many tutorials and articles and it should be working... but I guess not?
Can you check if hymnsData is undefined? After the const hymnsData = data.get('all'); line.
If so, that would explain the issue - you are correctly setting the locData but then overwriting it immediately after. If that is the case, I would add hymnsData to the if condition if (hymnsData && lastChangeLocal) { ... }
If you log songs and allSongs right before the return (, do you see ever see that they are populated, briefly?
Another thing I'd do to debug, is comment out the
setAllSongs(hymnsData);
setSongs(hymnsData);
lines and see if it is working as expected with locData only
The problem was with the sortHymns() method. I moved it from it's own method to the useEffect and it's working now.
I am really newReact Native Testing Library. My app basically works like this: it fetched the data and display on my to my as Text format, I used jsonplace holder api. This is app-demo. I have have created one Text where I define test role="header". I want to test the Text, does it work properly under role="header". I make a fake data and try to test it. I can able target the role from the component but I don't how to get the expected data. I tried with toBe, getByText but each time I am getting error: TypeError: toBe is not function.
This is my app component
const [state, setState] = React.useState([]);
React.useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/")
.then((response) => response.json())
.then((json) => setState(json));
}, []);
return (
<View style={styles.container}>
{state.map((i) => (
<Text role:'header'>{i.title}</Text>
))}
</View>
);
}
This is my test suite
import React from 'react';
import { fireEvent, render, cleanup, act } from '#testing-library/react-native';
import Json from './Json';
describe('<Json/>', () => {
afterEach(cleanup);
test('get data properly', async () => {
const component = <Json/>;
const { getByA11yRole } = render(component);
const header = await getByA11yRole('header');
console.log(header);
expect(header).toBe(/delectus aut autem/);
});
});
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.
Following Apollo's Recompose Patterns
https://www.apollographql.com/docs/react/recipes/recompose.html
I've created a simple ErrorScreen component which outputs the error.message and displays a retry button.
const ErrorScreen = ({ refetch, data }) => {
const handleRetry = () => {
refetch()
.then(({ data, loading, error, networkStatus }) =>
// SUCCESS: now what do I do with the result?
console.log('DBUG:refetch', { networkStatus, loading, error })
)
.catch(error => console.log({ error }));
};
return (
<View style={styles.container}>
<Text>{(data && data.error && data.error.message) || 'Something went wrong'}</Text>
<Button title="Retry" onPress={handleRetry} />
</View>
);
};
The component the ErrorScreen is being called from is pretty straight forward. Here's an example of a it's usage, just in case the context helps...
import React from 'react';
import { FlatList, View } from 'react-native';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { compose } from 'recompose';
import ErrorScreen, { renderIfError, setRefetchProp } from './ErrorScreen';
import LoadingScreen, { renderWhileLoading } from './LoadingScreen';
import Card from '../components/Card';
const EventList = ({ navigation, data: { me, error } }) => {
return (
<View style={styles.container}>
<FlatList
data={me.teams}
renderItem={({ item }) => <CardItem team={item} navigation={navigation} />}
keyExtractor={team => team.id}
/>
</View>
);
};
const options = {
fetchPolicy: 'cache-and-network',
};
const withData = graphql(userEvents, options);
export default compose(
withData,
renderWhileLoading(LoadingScreen),
setRefetchProp(),
renderIfError(ErrorScreen)
)(EventList);
Expected Result
I had hoped that calling refetch() would...
Cause the ErrorScreen disappear, being replaced by the LoadingScreen
If refetch were successful, automatically load the component that orignally errored with the new data
If refetch failed, the ErrorScreen would appear again
Actual Result
This is what I've witnessed
ErrorScreen persists and does not disappear
Original props.data.error is unchanged and still shows original error, w/o query result
Original props.data.netWorkStatus is still 8, indicating an error. The networkStatus Docs seem to indicate that the status should change to 4 - refetching but maybe I'm looking in the wrong place.
Original props.data.loading never changed, which I guess is expected behavior since from what I've read this only indicates first query attempt
My Question
How do I accomplish the expected behavior documented above? What am I missing?
Related Issues
https://github.com/apollographql/apollo-client/issues/1622
I found an workaround, check it out in my own question:
Apollo refetch not rerendering component
just under the text 'Update'