I am new to react native and simply can not find the reason for the infinity loop error I spent my last few hours with... Here is what is happening:
I have the following custom component
import { TouchableOpacity, StyleSheet, View, Text } from "react-native";
import { FontAwesome } from "#expo/vector-icons";
function AnswerContainer_CheckBox(props) {
const [checked, setChecked] = useState(false);
const checkedHandler =()=>{
if (checked == false) {
setChecked(true);
} else {
setChecked(false);
}
}
return (
<View>
<TouchableOpacity
onPress={() => {
checkedHandler();
props.true_1;
}
}
>
<View style={styles.mainContainer}>
<View style={styles.icon}>
<FontAwesome
name={checked == true ? "check-square" : "square-o"}
size={24}
color={checked == true ? "#3787FF" : "#BFD3E5"}
/>
</View>
<Text style={styles.checkButtonText}>{props.title}</Text>
</View>
</TouchableOpacity>
</View>
);
}
I use that Component in my screen like this:
function myScreen(props) {
const [true1, setTrue1] = useState(false);
const setTrue_1_Handler = () => {
switch (true1) {
case false:
setTrue1(true);
alert("true");
break;
case true:
setTrue1(false);
break;
}
};
return (
<SafeAreaView>
<AnswerContainer_CheckBox true_1={setTrue_1_Handler()} title="Test_1" />
<AnswerContainer_CheckBox title="Test_2" />
<AnswerContainer_CheckBox title="Test_3" />
</SafeAreaView>
);
}
Whenever I navigate to "myScreen", the infite loop error apperas and crashes the app - even though I didn't press that button yet.
Any ideas? I know the issue has come up a few times but I still didn't make it work (useEffect didn't help somehow...).
Thanks
Related
I am building a small quiz in react native. On my screen, I want the user to chose several correct answers from a choice of 4-6 options. I build a custom checkbox for that. If the correct answers are checked (and all wrong answers are unchecked) the user should get a message that the answer was correct.
Here is the custom checkbox component. I only included the code for three boxes to make the code a bit shorter:
import { TouchableOpacity, StyleSheet, View, Text } from "react-native";
import { FontAwesome } from "#expo/vector-icons";
function AnswerContainer_CheckBox(props) {
const [userInput, setUserInput] = useState("");
const answerHandler = () => {
if (userInput == props.finalAnswer) {
dispatch(answerTrue());
}
};
const [checked_1, setChecked_1] = useState(false);
const [checked_2, setChecked_2] = useState(false);
const [checked_3, setChecked_3] = useState(false);
/*Visibility
if set to false via props, the checkbox won't show
*/
const [box_1_Visibility, setBox_1_Visibility] = useState(
props.box_1_Visibility
);
const [box_2_Visibility, setBox_2_Visibility] = useState(
props.box_2_Visibility
);
const [box_3_Visibility, setBox_3_Visibility] = useState(
props.box_3_Visibility
);
/* Functions to chech and uncheck ityems*/
const checkedHandler_1 = () => {
if (checked_1 == false) {
setChecked_1(true);
setUserInput(userInput + props.box_1_Letter);
answerHandler();
} else {
setChecked_1(false);
setUserInput(userInput.replace(props.box_1_Letter,""));
answerHandler();
}
};
const checkedHandler_2 = () => {
if (checked_2 == false) {
setChecked_2(true);
setUserInput(userInput + props.box_2_Letter);
answerHandler();
} else {
setChecked_2(false);
setUserInput(userInput.replace(props.box_2_Letter,""));
answerHandler();
}
};
const checkedHandler_3 = () => {
if (checked_3 == false) {
setChecked_3(true);
setUserInput(userInput + props.box_3_Letter);
answerHandler();
} else {
setChecked_3(false);
setUserInput(userInput.replace(props.box_4_Letter,""));
answerHandler();
}
};
return (
<View>
{/* Checkbox 1 */}
<TouchableOpacity
onPress={() => {
checkedHandler_1();
}}
>
<View
style={
(box_1_Visibility === true && styles.mainContainer) || styles.hide
}
>
<View style={styles.icon}>
<FontAwesome
name={checked_1 == true ? "check-square" : "square-o"}
size={24}
color={checked_1 == true ? "#3787FF" : "#BFD3E5"}
/>
</View>
<Text style={styles.checkButtonText}>{props.box_1_Label}</Text>
</View>
</TouchableOpacity>
{/* Checkbox 2 */}
<TouchableOpacity
onPress={() => {
checkedHandler_2();
}}
>
<View
style={
(box_2_Visibility === true && styles.mainContainer) || styles.hide
}
>
<View style={styles.icon}>
<FontAwesome
name={checked_2 == true ? "check-square" : "square-o"}
size={24}
color={checked_2 == true ? "#3787FF" : "#BFD3E5"}
/>
</View>
<Text style={styles.checkButtonText}>{props.box_2_Label}</Text>
</View>
</TouchableOpacity>
{/* Checkbox 3 */}
<TouchableOpacity
onPress={() => {
checkedHandler_3();
}}
>
<View
style={
(box_3_Visibility === true && styles.mainContainer) || styles.hide
}
>
<View style={styles.icon}>
<FontAwesome
name={checked_3 == true ? "check-square" : "square-o"}
size={24}
color={checked_3 == true ? "#3787FF" : "#BFD3E5"}
/>
</View>
<Text style={styles.checkButtonText}>{props.box_3_Label}</Text>
</View>
</TouchableOpacity>
</View>
);
}
So what is happening here: onPress the checkedHandler-function checks the state. If "false" it will change it to "true". If it is "true" it will change to "false". Depending on the state, the style of the checkbox will change. The checkedHandler-function will also update the string within "userInput" depending on the state. The content of the string is catched via props from the parent component ("box_1_Letter" etc).
This is how I added the component in my screen/parent component:
<AnswerContainer_CheckBox
finalAnswer={"AC"}
box_1_Visibility={true}
box_2_Visibility={true}
box_3_Visibility={true}
box_4_Visibility={true}
box_5_Visibility={false}
box_6_Visibility={false}
box_1_Label={"Shanghai"}
box_1_Letter={"A"}
box_2_Label={"Paris"}
box_2_Letter={"B"}
box_3_Label={"New York"}
box_3_Letter={"C"}
box_4_Label={"Berlin"}
box_4_Letter={"D"}
/>
As you can see I first define how many boxes should be visible and I also add a label to each box and the associated "letter". "finalAnswer" contains the correct answer.
Now comes my problem: let's say in my example "Shanghai" and "New York" are the correct answers. Both boxes have to be checked while all other boxes have to be unchecked. How do I check that within the parent component/Screen?. My solution does not work. The user would have to check the boxes in the right order (and even then it somehow didn't work). The solution would also only be available within the component, not the parent. I do not want to create a global state with redux for this.
Any help appreciated (be aware: I am pretty new to this :-)
this modified answerHandler should solve your problem
const answerHandler = () => {
// if input length equals result length
if (userInput.length === props.finalAnswer.length) {
const inclusionMap = Array.from(props.finalAnswer).map((char) => {
return userInput.includes(char);
});
// if all the characters are included
if (inclusionMap.every((bool) => bool === true)) {
dispatch(answerTrue());
}
}
};
I am trying to resolve the error Non-serializable values were found in the navigation state. Alert > params.action[0].onPress (Function) of React Native navigation. I don't think the function is not passed to the param like the error points out, but it kept returning this same error every time I pressed the icon. I'd appreciate any suggestions or comments.
export default function Alert({ route, navigation }) {
const { colors } = useTheme();
const { t } = useTranslation();
const { title, message, action, option, type } = route?.params;
const success = type === "success";
useEffect(() => {
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
() => !option?.cancelable
);
return () => backHandler.remove();
}, [option?.cancelable]);
const renderButtonFirst = () => {
const firstTitle = action?.[0]?.text ?? t("close");
const onPressNo = action?.[0];
return (
<TouchableOpacity
onPress={() => {
onPressNo?.onPress();
if (option?.cancelable) navigation.goBack();
}}
>
<Text>
{firstTitle}
</Text>
</TouchableOpacity>
);
};
const renderButtonSecond = () => {
const secondTitle = action?.[1]?.text;
const onPressYes = action?.[1];
if (title && onPressYes) {
return (
<TouchableOpacity
onPress={() => {
onPressYes?.onPress();
if (option?.cancelable) navigation.goBack();
}}
>
<Text>
{secondTitle}
</Text>
</TouchableOpacity>
);
}
};
return (
<View>
<Icon name={success ? "check-circle" : "question-circle"} />
</View>
<View>
<Text>
{title}
</Text>
<Text>
{message}
</Text>
</View>
<View >
{renderButtonFirst()}
{renderButtonSecond()}
</View>
</View>
</View>
);
}
And this is the parent component just in case. But this error is from the Alert component as it says.
const onOpen = (type, title, link) => {
Alert.alert({
title: title,
message: `${t("do_you_want_open")} ${title} ?`,
action: [
{
text: t("cancel"),
onPress: () => console.log("Cancel Pressed"),
style: "cancel",
},
{
text: t("done"),
onPress: () => {
switch (type) {
case "web":
Linking.openURL(link);
break;
case "phone":
Linking.openURL("tel://" + link);
break;
case "email":
Linking.openURL("mailto:" + link);
break;
case "address":
Linking.openURL(link);
break;
}
},
},
],
});
};
{product?.website.length > 0 && (
<TouchableOpacity
onPress={() => {
onOpen("web", t("Website"), product?.website);
}}
>
<View>
<Image
source={Images}
/>
</View>
</TouchableOpacity>
)}
UPDATE 4/1
This is the Navigation component just in case;
import AlertScreen from "#screens/Alert";
export default function Navigator() {
...
return (
<AppearanceProvider>
<NavigationContainer theme={theme}>
<RootStack.Screen
name="Alert"
component={AlertScreen}
gestureEnabled: false,
}}
/>
</RootStack.Navigator>
</NavigationContainer>
</AppearanceProvider>
);
}
From the react navigation docs
This can happen if you are passing non-serializable values such as
class instances, functions etc. in params. React Navigation warns you
in this case because this can break other functionality such state
persistence, deep linking etc.
If you don't use state persistence or deep link to the screen which
accepts functions in params, then the warning doesn't affect you and
you can safely ignore it. To ignore the warning, you can use
YellowBox.ignoreWarnings.
If you are using react-native version > 0.63, use:
import { LogBox } from 'react-native';
LogBox.ignoreLogs([ 'Non-serializable values were found in the
navigation state', ]);
I also got bitten by this. You cannot pass non-simple objects to the navigation.
The problem is not "directly" in the code you posted but somewhere else. Either the go-back triggered the problem "once more" or there is somewhere a line like:
navigation.navigate('Alert', { action: {onPress: some_function }, /* rest */ }
In any case, the problem is that action comes from the parameters and is expected to have am onPress function. You cannot serialize a function an thus cannot model it like that.
Solution: Put that logic into a service and the parameters into the route, something like:
export Service {
do(actionDescription: {type: string, payload: any}) {
if (actionDescription.type === 'log') console.log(actionDescription.payload); // you get the idea
}
}
// in Alert
const onPressNo = () => Service.do(action?.[0].action);
// somewhere:
navitation.navigate('Alert', {action: [{action: {type: 'log', payload: 'Cancel Pressed'} /* complete... */]
So, past only simple objects to the navigation route. Use a pseudo-Command pattern, where the command state is passed into the route and the trigger is centralized somewhere else.
I dont understand how to hide and show Components dependet und a state. I came up with the following code, but i think its wrong, because the UI isnt being update after i press the "login" button. Is there a general way to do this? I also aks myself how to handle changing between "Dialogs". For example i want that when a Button is clicked, that the current Dialog is closed and the target Dialog is renderd. How could i do this?
import React from 'react';
import { ImageBackground, StyleSheet, TouchableOpacity, Text, View, TextInput} from 'react-native';
function WelcomeScreen(props) {
var loginDialog = {
visible: false
};
var signUpDialog = {
visible: false
};
var welcomeDialog = {
visible: true
};
const login = loginDialog.visible ? (
<View style={styles.loginDialog}>
<TextInput style={styles.input}/>
</View>
) : null;
const signup = signUpDialog.visible ? (
<View style={styles.signUpDialog}>
<TextInput style={styles.input}/>
</View>
) : null;
const welcome = welcomeDialog.visible ? (
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={changeState("login")}style={[styles.button, styles.login]}>
<Text style={styles.text}>Log In</Text>
</TouchableOpacity>
<TouchableOpacity onPress={changeState("signup")} style={[styles.button, styles.signUp]}>
<Text style={styles.text}>Sign Up</Text>
</TouchableOpacity>
</View>
) : null;
function changeState(mode){
if(mode === "login"){
console.log("Yes");
welcomeDialog.visible = false;
loginDialog.visible = true;
}else if(mode === "signup"){
welcomeDialog.visible = false;
signUpDialog.visible = true;
}
}
return (
<ImageBackground style={styles.background}
source={require('../assets/welcome_background.jpg')}>
{login}
{signup}
{welcome}
</ImageBackground>
);
}
export default WelcomeScreen;
Seems like you are new to react you should read about state https://reactjs.org/docs/state-and-lifecycle.html.
You should use useState in functional component or setState in class component also I really recomend to use third party library for dialogs/modals.
https://github.com/mmazzarolo/react-native-dialog
https://github.com/react-native-modal/react-native-modal
import React, {useState} from 'react';
import { ImageBackground, StyleSheet, TouchableOpacity, Text, View, TextInput} from 'react-native';
function WelcomeScreen(props) {
const [loginDialog,setLoginDialog] = useState(false);
const [signUpDialog ,setSignUpDialog ] = useState(false);
const [welcomeDialog ,setWelcomeDialog ] = useState(false);
const login = loginDialog ? (
<View style={styles.loginDialog}>
<TextInput style={styles.input}/>
</View>
) : null;
const signup = signUpDialog? (
<View style={styles.signUpDialog}>
<TextInput style={styles.input}/>
</View>
) : null;
const welcome = welcomeDialog? (
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={changeState("login")}style={[styles.button, styles.login]}>
<Text style={styles.text}>Log In</Text>
</TouchableOpacity>
<TouchableOpacity onPress={changeState("signup")} style={[styles.button, styles.signUp]}>
<Text style={styles.text}>Sign Up</Text>
</TouchableOpacity>
</View>
) : null;
const changeState = (mode) => {
if(mode === "login"){
console.log("Yes");
setWelcomeDialog(false);
setLoginDialog(true);
}else if(mode === "signup"){
setWelcomeDialog(false);
setSignUpDialog(true);
}
}
return (
<ImageBackground style={styles.background}
source={require('../assets/welcome_background.jpg')}>
{login}
{signup}
{welcome}
</ImageBackground>
);
}
export default WelcomeScreen;
I'm pretty new to the idea of states in react native and have trouble using them to change the states of the components. I have three text components one which is a question and another two are answers (yes and no), and another text component that checks if my answer to the question is write, so the check text component. When I click on yes for a certain question the state of the last check component should change to 'right' if the answer was yes or to 'wrong' if the answer is no. So basically, this is what I want to do :
This is what I have so far:
This is the error I get :
This is the code I have so far:
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Alert, ScrollView, Image } from 'react-native';
// import TextComp from './components/Home2'
export default function App() {
const [answer, setAnswer] = useState('answer');
const [correctAns, setCorrectAns] = useState('');
const useEffect = () => {
if (answer == 'answer') {
setCorrectAns('please answer!');
} else if (answer == 'Yes') {
setCorrectAns('Right');
} else if (answer == 'No') {
setCorrectAns('Wrong');
};
};
const corAns = () => { Alert('Your answer was ', correctAns) }
return (
<ScrollView style={styles.container}>
<View style={{ alignItems: 'center' }}>
<Image source={require('./images/pic.jpeg')} style={styles.uriImg} />
<Text style={styles.title}>Is this Mt. Fuji?</Text>
<TouchableOpacity
onPress={() => setAnswer('Yes')}>
<Text style={styles.text}>Yes</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setAnswer('No')}>
<Text style={styles.text}>No</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={corAns}>
<Text style={styles.text2}>Check</Text>
</TouchableOpacity>
<Image source={require('./images/yellow.png')} style={styles.uriImg} />
<Text style={styles.title}>Is this red color?</Text>
<TouchableOpacity
onPress={() => setAnswer('Yes')}>
<Text style={styles.text}>Yes</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setAnswer('No')}>
<Text style={styles.text}>No</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={corAns}>
<Text style={styles.text2}>Check</Text>
</TouchableOpacity>
</View>
</ScrollView>);
}
Can anyone tell me how it could be done? Thanks!
use useEffect like this
useEffect(() => {
if (answer === 'Yes') {
Alert.alert('Right')
}
if (answer === 'No') {
Alert.alert('Wrong')
}
}, [ answer ])
or by pressing check button
const checkAnswer = () => {
if (answer === 'Yes') {
Alert.alert('Right')
}
if (answer === 'No') {
Alert.alert('Wrong')
}
if (answer === 'answer') {
Alert.alert('please answer')
}
}
set check button onPress={checkAnswer}
I'm pretty new to Native React. I just went through the Expo tutorial and I am trying to make a Button that can consolidate the TouchableOpacity and Text into one component for re-usability.
I keep getting "Invariant Violation: Button(): Nothing was returned from render." I also referenced here.
Stylesheet is not in code below, not a concern.
Thanks!
import React from 'react';
import { Image, Platform, StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as Sharing from 'expo-sharing';
function Button(props)
{
return
(
<TouchableOpacity
onPress={props.command} style = {styles.button}>
<Text style={styles.buttonText}>{props.t}</Text>
</TouchableOpacity>
);
}
export default function App() {
const [selectedImage, setSelectedImage] = React.useState(null);
let openImagePickerAsync = async() => {
let permissionResult = await ImagePicker.requestCameraRollPermissionsAsync();
if (permissionResult.granted === false)
{
alert("Permission to access camera roll is required!");
return;
}
let pickerResult = await ImagePicker.launchImageLibraryAsync();
if (pickerResult.cancelled === true)
{
return;
}
setSelectedImage({localUri:pickerResult.uri});
};
let openShareDialogAsync = async() => {
if (!(await Sharing.isAvailableAsync()))
{
alert(`The image is available for sharing at: ${selectedImage.remoteUri}`);
return;
}
Sharing.shareAsync(selectedImage.localUri);
};
if (selectedImage !== null)
{
return(
<View style={styles.container}>
<Image
source ={{uri:selectedImage.localUri}}
style={styles.thumbnail}
/>
<TouchableOpacity onPress={openShareDialogAsync} style={styles.button}>
<Text style={styles.buttonText}>Share this photo</Text>
</TouchableOpacity>
</View>
);
}
return (
<View style={styles.container}>
<Image source={{uri: "https://i.imgur.com/TkIrScD.png"}} style={styles.logo} />
<Text style = {styles.instructions}>
To share a photo from your phone with a friends, just press the button below!
</Text>
<Button command = {openImagePickerAsync} t = "Pick a photo"/>
{/*
<TouchableOpacity
onPress={openImagePickerAsync} style = {styles.button}>
<Text style={styles.buttonText}>Pick a photo</Text>
</TouchableOpacity>
*/}
</View>
);
}
Nothing wrong with your code only thing is your return statement. When you place the opening bracket in the next line it will not return anything and your actual component code will be unreachable as it will be considered as a separate block. Just change it to the code below.
function Button(props) {
return (
<TouchableOpacity onPress={props.command} style={styles.button}>
<Text style={styles.buttonText}>{props.t}</Text>
</TouchableOpacity>
);
}