App crashes on API call in frameProcessor React Native - javascript

I am using a dependency called vision-camera-code-scanner for QR code scanning in my React Native app. I am getting QR code scan data properly. But i need to pass that data to make an API call. But when i try to do that, it crashes the application. Not sure what should i do here.
Here's my component:
import React, { useState, useCallback, useEffect, useMemo } from "react";
import { StyleSheet, Text } from "react-native";
import {
Camera,
useCameraDevices,
useFrameProcessor,
} from "react-native-vision-camera";
import { useDispatch, useSelector } from "react-redux";
import * as appActions from "../../../redux/app/app.actions";
import { BarcodeFormat, scanBarcodes } from "vision-camera-code-scanner";
interface ScanScreenProps {}
const Scan: React.FC<ScanScreenProps> = () => {
const [hasPermission, setHasPermission] = useState(false);
const devices = useCameraDevices();
const device = devices.back;
const dispatch = useDispatch();
const validateQRStatus = useSelector(validationQRSelector);
const frameProcessor = useFrameProcessor((frame) => {
"worklet";
const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
checkInverted: true,
});
if (detectedBarcodes?.length !== 0) {
const resultObj = JSON.parse(detectedBarcodes[0].rawValue);
const paramData = `token:${Object.values(resultObj)[0]}`;
validate(paramData);
}, []);
const validate = useCallback((param: string) => dispatch(appActions.validateQR(param)));
useEffect(() => {
(async () => {
const status = await Camera.requestCameraPermission();
setHasPermission(status === "authorized");
})();
}, []);
return (
device != null &&
hasPermission && (
<>
<Camera
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
frameProcessor={frameProcessor}
frameProcessorFps={5}
/>
{/* {barcodes.map((barcode, idx) => (
<Text key={idx} style={styles.barcodeTextURL}>
{barcode.barcodeFormat + ": " + barcode.barcodeText}
</Text>
))} */}
<Text style={styles.barcodeTextURL}>camera</Text>
</>
)
);
};
export default Scan;
const styles = StyleSheet.create({
barcodeTextURL: {
fontSize: 20,
color: "white",
fontWeight: "bold",
alignSelf: "center",
},
});

Your problem is that a worklet is run in a separate JS thread. If you need to call any function from your main thread you need to use runOnJS (https://docs.swmansion.com/react-native-reanimated/docs/next/api/miscellaneous/runOnJS/)
import { runOnJS } from 'react-native-reanimated';
const frameProcessor = useFrameProcessor((frame) => {
"worklet";
const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
checkInverted: true,
});
if (detectedBarcodes?.length !== 0) {
const resultObj = JSON.parse(detectedBarcodes[0].rawValue);
const paramData = `token:${Object.values(resultObj)[0]}`;
runOnJS(validate)(paramData);
}, []);

Related

Invalid Hook Call, returning a function inside of custom hook that also using the useContext hook

I'm trying to write a custom hook that returns a connectWallet function that i can use in components to connect Metamask but i get 'Invalid Hook Call' error.
The 'connect' function that i imported from 'utils/wallet' is making user to connect to my website via Metamask chrome extension.
Inside of the WalletContext.js
import { createContext, useContext, useEffect, useState } from "react";
import {
connect,
currentWallet,
walletListener,
} from "../utils/wallet";
export const WalletContext = createContext();
const WalletProvider = ({ children }) => {
const [wallet, setWallet] = useState();
useEffect(() => {
currentWallet(setWallet);
walletListener(setWallet);
});
const data = {
wallet,
setWallet,
};
return (
<WalletContext.Provider value={data}>{children}</WalletContext.Provider>
);
};
const connectWallet = () => {
const { setWallet } = useContext(WalletContext);
connect(setWallet);
};
export const useConnect = () => {
return { connectWallet };
};
export const useWallet = () => useContext(WalletContext);
export default WalletProvider;
Inside of the ConnectButton component
import { Button } from "#nextui-org/react";
import { Icon } from "#iconify/react";
import { useConnect, useWallet } from "../context/WalletContext";
export default function ConnectButton() {
const { wallet, setWallet } = useWallet();
const { connectWallet } = useConnect();
return (
<Button
auto
css={{
// position: "absolute",
// top: "0px",
// right: "50px",
zIndex: 1,
}}
color={"primary"}
icon={<Icon icon="entypo:wallet" width="28px" />}
onPress={connectWallet}
>
{wallet
? wallet.substring(0, 4) + "..." + wallet.substring(wallet.length - 4)
: "Connect Metamask"}
</Button>
);
}

Expo SecureStore not saving correctly (React native, typescript)

I'm working on a mobile phone application with Stripe and Expo Bar Code Scanner. When you start the application, if you gave the permissions for using the camera, you will can scan bar codes. Bar Codes only contains the id of the scanned item. If it exists, two buttons (+/-) will appear in order to choose the amount for the item. If it doesn't exists, nothing happens. When the amount changes, I save in SecureStore the id of the item as the key and the amount as the value.
The problem is when I move on others screens (with React Navigation) and I came back to scan and I rescan the same item, the amount resets to 0. If you don't give the permissions for the camera, it displays a list of available items when you can choose the amount (+/-) buttons and similar problem.
Here the concerned two files :
ItemListComponent.tsx
import { Button, FlatList, View, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { useState } from 'react';
export const ItemComponent = (props: any) => {
const [amount, setAmount] = useState<number>(0);
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
const save = async () => {
await SecureStore.setItemAsync(props.item.id.toString(), amount.toString());
}
return (
<View>
<Text>{props.item.name}</Text>
<Button
onPress={() => {
setAmount(amount + 1);
save();
}}
title='+'
/>
{amount > 0 &&
<Button
onPress={() => {
setAmount(amount - 1);
save();
}}
title='-'
/>
}
</View>
);
};
export const ItemListComponent = (props: any) => {
return (
<FlatList
data={props.items}
renderItem={({ item }) =>
<ItemComponent key={item.id} item={item} />
}
/>
);
};
BarCodeScannerComponent.tsx
import { BarCodeScanner } from 'expo-barcode-scanner';
import { useState } from 'react';
import { StyleSheet } from 'react-native';
import { ItemComponent } from './ItemListComponent';
import Items from '../models/ItemsModel';
export const BarCodeScannerComponent = () => {
const [item, setItem] = useState<Items>();
const getItem = async ({ data }: any) => {
const response = await fetch(`http://192.168.1.81:8000/items/${data}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const json = await response.json();
setItem(json);
}
}
return (
<View style={styles.container}>
<BarCodeScanner
onBarCodeScanned={getItem}
style={StyleSheet.absoluteFillObject}
/>
{(item !== null && item !== undefined) && <ItemComponent key={item.id} item={item} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
});
Thanks for help !
It looks like you never call getAmount, and if you did call it you'd get infinite recursion.
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
should be
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
}
getAmount();
or, probably even better:
const getAmount = async () => {
const storeAmount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount !== parseInt(storeAmount)) {
setAmount(parseInt(storeAmount));
}
}
useEffect(() => {
getAmount();
}, [props.item.id]);
otherwise, every time it renders you'll call setAmount which will trigger a rerender

React Native - Type Script: How to save dark mode toggle state even after turning off the application?

What is the correct way so that I can save the dark mode switch even after turning off the application?
I want to use the use-state-persist library to achieve the goal.
In my example I show app.tsx , Preferences.tsx, ViewField.tsx .
So that it will be possible to understand how the logic is built
DarkModeContext
import React from 'react';
interface DarkMode {
isDarkMode: boolean;
setDarkMode: () => void;
}
export const DarkModeContext = React.createContext<DarkMode>({} as DarkMode);
this is the app.tsx
import React, { useEffect, useState } from 'react';
import { syncStorage } from 'use-state-persist';
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const Drawer = createDrawerNavigator();
const initStorage = async () => await syncStorage.init();
const toggleDarkMode = () => {
setDarkMode(!isDarkMode);
};
return (
<DarkModeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
<NavigationContainer>
<Drawer.Navigator
drawerContent={SideMenu}
screenOptions={{
drawerPosition: 'right',
headerShown: false,
drawerType: 'front',
}}
>
<Drawer.Screen name='HomeScreen' component={StackNavigator} />
</Drawer.Navigator>
</NavigationContainer>
</DarkModeContext.Provider>
);
};
export default App;
this is the Preferences.tsx
import React, { useContext, useState } from 'react';
import ViewField from './ViewField';
import { DarkModeContext } from '~/context/DarkModeContext';
const Preferences = () => {
const { isDarkMode, toggleDarkMode } = useContext(DarkModeContext);
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggleDarkMode} />
</View>
);
};
export default Preferences;
this is the ViewField.tsx
import { View, Text, Switch } from 'react-native';
import React from 'react';
import styles from './ViewFieldStyles';
import { useContext } from 'react';
import { DarkModeContext } from '~/context/DarkModeContext';
type Props = {
title: string;
isEnabled: boolean;
setValue: () => void;
};
const ViewField = ({ title, isEnabled, setValue }: Props) => {
const { isDarkMode } = useContext(DarkModeContext);
return (
<View style={isDarkMode ? styles.optionViewDark : styles.optionView}>
<View style={styles.sameRowTextView}>
<Text style={isDarkMode ? styles.optionTextDark : styles.optionText}>{title}</Text>
<View style={styles.switchView}>
<Switch
trackColor={
isDarkMode
? { false: 'rgba(255, 255, 255, 0.38)', true: 'rgba(187, 134, 252, 0.38)' }
: { false: '#767577', true: 'rgba(4, 76, 163, 0.38)' }
}
onValueChange={setValue}
value={isEnabled}
/>
</View>
</View>
</View>
);
};
export default ViewField;
Keep in mind that there seems to be some problems in use-state-persist using Boolean values. Furthermore, the latest published version of this library is from 2020.
However, the use-state-persist library just seems to be a wrapper around AsyncStorage, which is very well maintained. I would encourage you to use this library instead.
In your case, this could be implemented as follows:
Store the actual setter of the state in the context,
Create an effect that accesses the async storage on mount of the application: if there exists a value for the corresponding key, set the state of the context, if not, then do nothing.
In the Preferences component, store a new state in the async storage as well.
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const contextValue = React.useMemo(() => ({
isDarkMode,
setDarkMode
}), [isDarkMode])
React.useEffect(() => {
const load = async () => {
const value = await AsyncStorage.getItem('isDarkMode');
if (value !== null) {
setDarkMode(JSON.parse(value));
}
}
load();
}, [])
return (
<DarkModeContext.Provider value={contextValue}>
...
};
In the Preferences component, set the state and save it to the local storage.
const Preferences = () => {
const { isDarkMode, setDarkMode } = useContext(DarkModeContext);
async function toggle() {
const newValue = JSON.stringify(!isDarkMode);
await AsyncStorage.setItem('isDarkMode', newValue);
setDarkMode(prev => !prev);
}
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggle} />
</View>
);
}

React Native application freezes while fetching API

Hi I have very common problem, react native application freezes while fetching API,
It is like when you fetching you cannot click or do anythink till your fetching ends.
This is my code where I call function which fetchs APi
import React, { useEffect, useState } from "react";
import { StyleSheet, View, FlatList } from "react-native";
// Api`s
import { topCategory } from "../../api/appModels/category";
import { banners } from "../../api/appModels/slider";
import { dinamicBlocks } from "../../api/appModels/product";
// Hooks
import useApi from "../../Hooks/useApi";
// UI-Components
import Container from "../../uicomponents/General/Container";
import BannerSlider from "../../uicomponents/Slider/BannerSlider";
import Screen from "../../uicomponents/General/Screen";
import TopProductPattern from "../../uicomponents/Category/pattern/TopProductPattern";
import SliderPlaceholder from "../../uicomponents/Skeleton/Sliders/SliderPlaceholder";
import CategoryAvatarPlaceholder from "../../uicomponents/Skeleton/Category/CategoryAvatarPlaceholder";
import CategoryBlocks from "../../uicomponents/Product/blocks/CategoryBlocks";
import Header from "../../uicomponents/header/Header";
import ActivityIndicator from "../../uicomponents/Loaders/ActivityIndicator";
const HomeScreen = () => {
const {
data: topCategoryList,
loading: topCategoryLoading,
request: categoryRequest,
} = useApi(topCategory);
const {
data: bannersList,
loading: bannerLoading,
request: bannersRequest,
} = useApi(banners);
const {
loading: blocksLoading,
request: blocksRequest,
} = useApi(dinamicBlocks);
const [blocks, setBlocks] = useState([]);
const [indexing, setIndexing] = useState(1);
const [countBlocks, setCountBlocks] = useState(0);
const [loader, setLoader] = useState(false);
useEffect(() => {
// Calling Api`s
categoryRequest();
bannersRequest();
blocksRequest((item) => {
setIndexing(indexing + 1);
setBlocks(item["blocks"]);
setCountBlocks(item["count"]);
}, 1);
}, []);
const loadMore = () => {
if (!blocksLoading) {
blocksRequest((item) => {
setIndexing(indexing + 1);
setBlocks(blocks.concat(item["blocks"]));
console.log(item);
}, indexing);
setLoader(indexing != countBlocks);
}
};
return (
<Screen>
<FlatList
data={blocks}
keyExtractor={(item) => item.categories.toString()}
ListHeaderComponent={
<>
<Header />
{bannerLoading ? (
<SliderPlaceholder />
) : (
<BannerSlider data={bannersList} dots />
)}
<Container>
<View style={{ paddingTop: 10 }}>
{topCategoryLoading ? (
<CategoryAvatarPlaceholder />
) : (
<TopProductPattern data={topCategoryList} />
)}
</View>
</Container>
</>
}
ListFooterComponent={<ActivityIndicator visible={loader} />}
renderItem={({ item }) => (
<CategoryBlocks title={item.blocks_name} data={item.categoriesList} />
)}
onEndReached={loadMore}
onEndReachedThreshold={0.1}
/>
</Screen>
);
};
export default HomeScreen;
const styles = StyleSheet.create({});
Here is my code which fetches API
import { useState } from "react";
const useApi = (apiFunc) => {
const [data, setData] = useState([]);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
const request = async (callBack = () => {}, ...args) => {
setLoading(true);
const response = await apiFunc(...args);
if (!response.ok) return setError(response.problem);
setLoading(false);
setError("");
setData(response.data);
if (response.ok) {
callBack(response.data);
}
};
return {
data,
error,
loading,
request,
setLoading,
setData,
};
};
export default useApi;
I think there is problem with RN-bridge HELP ME PLEASE !
Probably it's happened because you set onEndReachedThreshold={0.1} and when data was loaded one more time request will be send and it's make problem.
So you can increase this value for example to 0.7.
In the official react native website This is how it is explained onEndReachedThreshold:
"How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end of the content to trigger the onEndReached callback. Thus a value of 0.5 will trigger onEndReached when the end of the content is within half the visible length of the list."

How do I send a function parameter to AsyncStorage?

I want to send the parameter to the function submitLanguageSelection, which is userSelectedLanguage, to a custom hook I've written which (hopefully) saves that parameter to AsyncStorage. The user selects a language, either English or Arabic, from one of the two buttons.
This is my first time ever doing this. I've gotten very stuck.
I would like the submitLanguageSelection function to call the saveData function which is made available through the useLocalStorage hook. I would like the user's choice of language to be persisted in AsyncStorage so I can then later render the ChooseYourLanguageScreen according to whether the user has selected a language or not.
Here is the cutom hook, useLocalStorage:
import React from 'react';
import { Alert } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
const STORAGE_KEY = '#has_stored_value';
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async () => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, storedValue);
if (localValue !== null) {
setStoredValue(storedValue);
Alert.alert('Data successfully saved');
}
console.log('stored val', storedValue);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};
Here is the ChooseYourLanguageScreen:
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import useLocalStorage from '../hooks/useLocalStorage';
const ChooseYourLanguageScreen = ({ navigation }) => {
const [saveData, errorMessage] = useLocalStorage();
const submitLanguageSelection = (userSelectedLanguage) => {
//TODO: save the data locally
//TODO: navigate to welcome screen
// at the moment, the language choice isn't making it to useLocalStorage
if (userSelectedLanguage !== null) {
console.log('user selected lang', userSelectedLanguage);
saveData(userSelectedLanguage);
}
};
return (
<View style={styles.container}>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text style={styles.text}>This is the Choose Your Language Screen</Text>
<View style={styles.buttons}>
<View>
<Button
title={'English'}
onPress={() => submitLanguageSelection('English')}
/>
</View>
<View>
<Button
title={'Arabic'}
onPress={() => submitLanguageSelection('Arabic')}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
alignSelf: 'center',
},
buttons: {
backgroundColor: '#DDDDDD',
padding: 10,
},
});
export default ChooseYourLanguageScreen;
saveData() needs a parameter. You can provide a default value that uses storedValue that came from React.useState(), but when you call it with an explicit argument it will override that default.
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async (dataToSave = storedValue) => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, dataToSave);
if (localValue !== null) {
setStoredValue(dataToSave);
Alert.alert('Data successfully saved');
}
console.log('stored val', dataToSave);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};

Categories

Resources