I'm a beginner on React Native and I am getting this error when getItemsCount is called.
*Please Click on the Links to see images
https://i.stack.imgur.com/wbwjZ.png
This is the code for CartIcon.js:
import React, {useContext} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {CartContext} from './CartContext';
export function CartIcon({navigation}){
const {getItemsCount} = useContext(CartContext);
return(
<View style = {styles.container}>
<Text style = {styles.text}
onPress = {() => {
navigation.navigate('Cart');
}}
>Cart ({getItemsCount()}) </Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 10,
backgroundColor: '#515b8c',
height: 40,
padding: 15,
borderRadius: 38/2,
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#ccc',
fontWeight: 'normal',
},
});
https://i.stack.imgur.com/ABYHm.png
This is the code for CartContext.js:
import React, {createContext, useState} from 'react';
import {getProduct} from './productService.js';
export const CartContext = createContext();
export function CartProvider(props){
const [items, setItems] = useState([]);
function addItemToCart(id){
const product = getProduct(id);
setItems((prevItems) => {
const item = prevItems.find((item) => (item.id == id));
if(!item){
return [...prevItems, {
id,
qty: 1,
product,
totalPrice: product.price
}];
}
else{
return prevItems.map((item) => {
if(item.id == id){
item.qty++;
item.totalPrice += product.price;
}
return item;
});
}
});
}
function getItemsCount(){
return items.reduce((sum,item) => (sum+item.qty),0);
}
function getTotalPrice(){
return items.reduce((sum,item) => (sum+item.totalPrice),0);
}
return(
<CartContext.Provider
value = {{items,setItems,getItemsCount,addItemToCart,getTotalPrice}}>
{props.children}
</CartContext.Provider>
);
}
https://i.stack.imgur.com/HsXoY.png
Taking a guess, but I would think that your component is outside of the provider, please check that your CartIcon is actually inside of the CartContext.Provider, otherwise it won't have access to it.
please add this to CartContext.js:
const useCartContext = () => {
const context = useContext(CartContext);
if (context === undefined) {
throw new Error('useCartContext must be used within a CartContextProvider');
}
return context;
};
and
export { CartProvider, useCartContext };
Go to App.jsx and wrap the whole app with
<CartProvider>
// your app
</CartProvider>
Then in CartIcon.js import useCartContext and replace
const {getItemsCount} = useContext(CartContext);
with
const { getItemsCount } = useCartContext();
Let me know what happens. The idea is to create a hook, which is nicer, but the issue here is that your component needs to be inside a provider for it to have access to the context.
The Reason for MySide getting Error is forget to use return statement while creating Global context .
Check that Side also .
** -> 1 more Side can be not Using ContextProvider or wrapping in the App.jsx File App.js File ( Mostly people Forget ).
Related
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);
}, []);
I have a unit testing coverage report for a component MyCheckbox.js.
coverage
How do I test the onCheckmarkPress() function in MyCheckbox.js?
Here is the implementation of MyCheckbox.js:
import * as React from 'react';
import { Pressable, StyleSheet } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import { useState } from 'react';
/**
*
* #param {() => void} props.onUpdate called when checkbox is pressed
* #return {JSX.Element}
* #constructor
*/
const MyCheckbox = (props) => {
const [checked, onChange] = useState(false);
function onCheckmarkPress() {
onChange((prev) => {
let checked = !prev;
props.onUpdate(checked);
return checked;
});
}
return (
<Pressable
style={[styles.checkboxBase, checked && styles.checkboxChecked]}
onPress={onCheckmarkPress}
>
{checked && <Ionicons name="checkmark" size={24} color="black" />}
</Pressable>
);
};
const styles = StyleSheet.create({
checkboxBase: {
width: 35,
height: 35,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4,
borderWidth: 2,
borderColor: 'white',
backgroundColor: 'white',
},
checkboxChecked: {
backgroundColor: '#C4C4C4',
},
});
export default MyCheckbox;
This is how I attempt the test case:
import React from 'react'
import MyCheckbox from '../MyCheckbox';
import {fireEvent, render, screen} from "#testing-library/react-native";
import '#testing-library/jest-dom';
it("works", () => {
const onUpdateMock = jest.fn();
render(<MyCheckbox onUpdate={onUpdateMock} />);
expect(onUpdateMock).toHaveBeenCalledTimes(0);
expect(screen.queryByTestId("checkIcon")).toBeNull();
const pressable = screen.getByRole("pressable");
fireEvent.press(pressable);
expect(onUpdateMock).toHaveBeenCalledTimes(1);
expect(screen.queryByTestId("checkIcon")).toBeInTheDocument(); // check that the icon is rendered
});
However, I am getting the error "Unable to find an element with accessibilityRole: pressable". And will this cover the red lines marked in the coverage report?
You should test the behaviour of your component, so it would look sth like this:
Render the component
Find the Pressable component
Make sure that check icon is not displayed
emulate the click event on Pressable component or other event (touch?) it responds to
Check if check icon is displayed
This kind of test will give you the coverage and the functionality tested.
Please provide the Pressable source if you need more detailed answer, it is hard to tell if it is button or some other implementation.
You can use the React Testing Library to achieve all of above steps.
Assuming your MyCheckbox works like this:
const MyCheckbox = (props) => {
const [checked, onChange] = React.useState(false);
const onCheckmarkPress = () => {
onChange((prev) => {
let checked = !prev;
props.onUpdate(checked);
return checked;
})
}
return (
<button onClick={onCheckmarkPress}>
{checked && <IonIcon data-testid="checkIcon" name="checkmark" />}
</button>
);
};
You could test it like this:
import { fireEvent, render, screen } from "#testing-library/react";
import MyCheckBox from "../MyCheckbox";
it("should work", () => {
const onUpdateMock = jest.fn();
render(<MyCheckBox onUpdate={onUpdateMock} />);
expect(screen.queryByTestId("checkIcon")).not.toBeInTheDocument(); // check that the icon is not rendered
const btn = screen.getByRole("button"); // get the button (pressable)
fireEvent.click(btn); // click it
expect(screen.getByTestId("checkIcon")).toBeInTheDocument(); // check that the icon is displayed
expect(onUpdateMock).toHaveBeenCalledTimes(1); // make sure that the onUpdate function that was passed via props was clicked
});
I have spent days now and have read numerous articles and answers here and I can not wrap my head around this. Below is just my last attempt at this.
I just need to use data stored in Async Storage and use it inside App()
Can someone please take a look at this simple App() starting code and explain in the planest possible way how to resolve the promise here.
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import Icon from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '#react-native-async-storage/async-storage'
export default async function App () {
let [userData, setUserData] = useState({})
useEffect(() => {
storeData('test2')
getItem()
}, [])
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
const getItem = async () => {
const value = await AsyncStorage.getItem('#storage_Key')
return value
}
userData = getItem()
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
The <Text>Local storing: {userData}</Text> is allways a unresolved proise object.
I had some success with previous code where console.log(userData) did actually produce the wanted value but it is still not useable inside <Text>. I just dont get it.
Thank you in advance and please keep in mind I'm new to react native.
EDIT:
latest attemt:
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import Icon from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '#react-native-async-storage/async-storage'
export default async function App () {
let [userData, setUserData] = useState({})
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
storeData('test2')
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_Key')
if(value !== null) {
console.log(value)
return value
}
} catch(e) {
// error reading value
}
}
userData = await getData()
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
Now I get for some reason 4 test2 , correct values consoled logged but still get an error:
Error: Objects are not valid as a React child (found: object with keys {_U, _V, _W, _X}). If you meant to render a collection of children, use an array instead.
which is a promise object, and app fails to build.
Because you are not setting the state and getting the item on the renderer, not on any useEffect which is also a bad practice.
Try this
export default async function App () {
let [userData, setUserData] = useState('')
useEffect(() => {
storeData('test2')
}, [])
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
getItem()
} catch (e) {
// saving error
}
}
const getItem = async () => {
const value = await AsyncStorage.getItem('#storage_Key')
setUserData(JSON.stringify(value))
}
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
Fixed it:
import { StatusBar } from 'expo-status-bar'
import React, { useState, useEffect } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import Icon from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '#react-native-async-storage/async-storage'
const storeData = async value => {
try {
await AsyncStorage.setItem('#storage_Key', value)
} catch (e) {
// saving error
}
}
storeData('test2')
export default function App () {
let [userData, setUserData] = useState('')
useEffect(() => {
getData()
}, [])
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_Key')
if (value !== null) {
console.log(value)
setUserData(value)
userData = value
}
} catch (e) {
// error reading value
}
}
console.log(userData)
return (
<View style={styles.container}>
<Text>Local storing: {userData}</Text>
<StatusBar style='auto' />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
there shouldn't have been async in export default function App () {
getData() get data should have been iniciated inside useEffect
useEffect(() => {
storeData('test2')
getData()
}, [])
I have also set the state inside getData()
userData = value
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];
};
Using react hooks with firebase real time database with a project Im working on for the first time, the data is being retrieved and logs in the console. BUT I am unable go move out side the useEffect function to display values on the screen! Would really appreciate some help!
videoArray needs to be put in a FlatList, as you can see in the code bellow videoArray logs values in the console in useEffect function. However, once I move out that function to add it into a FlatList it is null because the values are in a local function.
My question is, how am I able to pass value of videoArray (in useEffect) into the FlatList?
import React, { useState, useEffect } from 'react'
import { FlatList, View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Center } from '../components/Center'
import { Video } from 'expo-av';
import firebase from '../firebase'
const videoRef = firebase.database().ref('videoCollaction');
export const FeedScreen = ({ }) => {
let [videoArray, setVideo] = useState([]);
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
videoArray = [];
childSnapshot.forEach((doc) => {
videoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
})
// able to log values only here
console.log('working:', videoArray);
});
// get video uri from firebase (testing)
// readVideo = () => {
// var collection = firebase.database().ref("videoCollactionvideo" + "/video").orderByValue();
// console.log('uri', collection);
// }
return (
<SafeAreaView>
<Text>Feed Screen</Text>
{/* null here: need values to show up here*/}
{console.log(" test",videoArray)}
<FlatList
data={videoArray}
renderItem={({ item, index }) => {
return (
<View>
<Text style={{ fontSize: 35, color: 'red' }}>Video:...</Text>
<TouchableOpacity onPress={() => console.log('pressed')}><Text style={{ color: 'blue' }}>Expand</Text></TouchableOpacity>
</View>
);
}} keyExtractor={({ item }, index) => index.toString()}>
</FlatList>
</SafeAreaView>
);
}
Try this:
useEffect(() => {
const temp = []; // temp array
videoRef.on("value", childSnapshot => {
childSnapshot.forEach(doc => {
temp.push({
key: doc.key,
video: doc.toJSON().video
});
});
setVideo(temp); // update state array
});
}, []);
It seems like you are trying to update the State Hook (videoArray), but you are doing it the wrong way (it shouldn't be modified directly). Instead, use the setVideo update method which you created with the Hook (let [videoArray, setVideo] = useState([]);):
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
newVideoArray = [];
childSnapshot.forEach((doc) => {
newVideoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
})
// able to log values only here
console.log('working:', newVideoArray);
setVideo(newVideoArray);
});
Check out Using the Effect Hook for more information on how to use this specific hook (the Optimizing Performance by Skipping Effects section might be especially of interest).
In essence, this functionality is similar to your Functional Component's stateful counterparts (React.Component or React.PureComponent), where:
Constructor is the only place where you should assign this.state directly. In all other methods, you need to use this.setState() instead.
Try this:
import React, { useState, useEffect } from 'react'
import { FlatList, View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Center } from '../components/Center'
import { Video } from 'expo-av';
import firebase from '../firebase'
const videoRef = firebase.database().ref('videoCollaction');
export const FeedScreen = ({ }) => {
let [videoArray, setVideoArray] = useState([]);
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
const newVideoArray = [];
childSnapshot.forEach((doc) => {
newVideoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
setVideoArray(newVideoArray);
})
// able to log values only here
console.log('working:', videoArray);
}, []);
console.log('State also working :) >> ', videoArray);
return (
<SafeAreaView>
<Text>Feed Screen</Text>
{/* null here: need values to show up here*/}
{console.log(" test",videoArray)}
<FlatList
data={videoArray}
renderItem={({ item, index }) => {
return (
<View>
<Text style={{ fontSize: 35, color: 'red' }}>Video:...</Text>
<TouchableOpacity onPress={() => console.log('pressed')}><Text style={{ color: 'blue' }}>Expand</Text></TouchableOpacity>
</View>
);
}} keyExtractor={({ item }, index) => index.toString()}>
</FlatList>
</SafeAreaView>
);
}