React Native | Why is my function running immediately, rather than just onPress? - react-native

I am just starting to grasp props in react native so I am hoping that this is a simple solution.
I want the state of the form to update to bring the user to the next page in the form and I want the state of the response to update as well - both when the user presses the button component (onPress).
However, what I'm seeing when I console.log is that the update state function is running immediately, rather than when the button is pressed - so it is going directly to the second "page" of the form.
Form Component
import React, {useState} from 'react';
import { View, Text} from 'react-native';
import Happiness from './Happiness';
const StarterForm = () => {
const [formStage, setFormStage] = useState(1)
const [happinessLevel, setHappinessLevel] = useState('')
console.log(formStage)
console.log(happinessLevel)
const increaseTheStage = (happiness) => {
setHappinessLevel(happiness)
setFormStage(formStage +1)
}
switch (formStage) {
case 1:
return (
<Happiness
passHappiness={increaseTheStage}
/>
)
case 2:
return (
<Text>This is the case of two</Text>
)
}
}
export default StarterForm;
Happiness component
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
const Happiness = (props) => {
return (
<View>
<Text>Which of the following best classifies your happiness?</Text>
<TouchableOpacity onPress={props.passHappiness('Excellent')}>
<Text>Excellent</Text>
</TouchableOpacity>
</View>
)
}
export default Happiness;
Expected Results
I expect the following when the first screen opens:
console.log(formStage) = "1"
console.log(happinessLevel) = ""

Using anonymous functions
You are calling the function right away, switch it to this:
<TouchableOpacity onPress={() => props.passHappiness('Excellent')}>
Now you have created an anonymous function which calls passHappiness with the parameter 'Excellent' illustrated below:
() => props.passHappiness('Excellent')
Using bind
You can also use the bind method to "bind" the parameter "excellent" to the function
<TouchableOpacity onPress={props.passHappiness.bind(this,'Excellent')}>
More on the bind method here.

Related

Adding a function to the passed this.props.onPress-function in a React Native app

I have changed all the TouchableOpacity-components to a custom component so I can add a universal function to all the buttons / clickable views in my application. I call the new component HapticButton.
All the HapticButton-components will contain onPress properties like this:
<HapticButton activeOpacity={1.0} onPress={() => { console.log("button was pressed"); }}>...</HapticButton>
And my HapticButton-class looks like this:
import React from 'react';
import { TouchableOpacity, Text} from 'react-native';
export default class HapticButton extends React.Component {
render() {
return (
<TouchableOpacity activeOpacity={this.props.activeOpacity} style={[this.props.style]} onPress={this.props.onPress}>
{this.props.children}
</TouchableOpacity>
);
}
vibrate() {
// Code that makes a haptic feedback when called
}
};
I succesfully pass on the onPress-property to my new HapticButton-component, but how do I merge together my the this.props.onPress-property with the vibrate()-function so that this gets called every time the HapticButton is pressed?
You can easily merge two function call together as below
<TouchableOpacity onPress={() => {
this.props.onPress();
this.vibrate();
}}>
or you can directly invoke this.props.onPress() in the vibrate function
<TouchableOpacity onPress={this.vibrate} ...
vibrate() {
this.props.onPress();
// Code that makes a haptic feedback when called
}
Not much differences based on your use case between the two ways I've shared, readability wise I think first way is better

bad setstate() when redirecting user with react-navigation?

I'm coding an onboarding process that depends on a state. For each "page" the user completes the state increments and returns the next JSX page. At the last step, I'm navigating the user to a page called explore page.
Below is the bug I'm getting when I don't have the setTimeout method around the navigation.navigate function. The full error trace leads back to the navigate function.
This is a snippet of the error:
index.js:1 Warning: Cannot update a component (ForwardRef(BaseNavigationContainer)) while rendering a different component (OnboardingTemplate). To locate the bad setState() call inside OnboardingTemplate, follow the stack trace as described in https://facebook.me/setstate-in-render
Each step works fine and it returns the JSX but it seems like that because the view hasn't finished rendering it throws the above error while loading the explore page. The error occurs even though the explore page gets shown. I've tried with useEffect and everything I could think of but I can't figure it out. And it seems weird if setTimeout is the right solution.
Here is the code:
import React, {useEffect, useState} from 'react'
import {View,ScrollView,StyleSheet} from 'react-native'
import Button from '../ui/atoms/Button'
import { useNavigation } from '#react-navigation/native'
let currentStep : JSX.Element;
export default function OnboardingTemplate(){
const navigation : any = useNavigation()
const [loaded,setLoaded] = useState(false);
useEffect(() => {
setLoaded(true)
})
const [onboardingStep, setOnboardingStep]:any = useState(0);
if (onboardingStep == 0){
currentStep=(
<ScrollView>
<View style={styles.topDistance}>
<Button onPress={() => setOnboardingStep(1)}
style={styles.inputBtn} title="Fortsæt (1/2)"/>
</View>
</ScrollView>
)
} else if (onboardingStep == 1){
currentStep=(
<ScrollView>
<View style={styles.topDistanceSmall}>
<Button onPress={() => setOnboardingStep(2)}
style={styles.inputBtn} title="Opret profil"/>
</View>
</ScrollView>
)
} else if (loaded) {
navigation.navigate("explore");
}
return (currentStep) ? currentStep : null ;
}

use useState hook, however the setter function is undefined at runtime

I am using useState hook in my react-native project. I have a screen which renders my custom component named MyComponent. The setter function of state is called in MyComponent 's onSelected callback.
import React, {useState} from 'react';
import MyComponent from './components/MyComponent'
const MyScreen=()=> {
...
const {parts, setParts} = useState(initialParts);
return (<View>
<MyComponent onSelected={()=> {
...
setParts(newParts)
}}/>
</View>)
}
...
MyComponent looks like this, in the onPress callback of TouchableOpacity, it calls the passed in onSelected function:
const MyComponent= ({onSelected})=> {
...
return (<TouchableOpacity onPress={()=>{
onSelected();
...
}}>
...
</TouchableOpacity>)
}
When I run my app on iOS emulator, the screen shows, when I tap on MyComponent, I get error TypeError: setParts is not a function. (In setParts(newParts)), 'setParts' is undefined.
Why I get this error?
Your destructuring here seems wrong:
const {parts, setParts} = useState(initialParts);
Shouldn't be this:
const [parts, setParts] = useState(initialParts);
?
Hmmm, it seems like you have to read the React official documentation to know more about UseState.
here is fix to your code:
const MyScreen = () => {
const [parts, setParts] = useState(initialParts) // see this fix.
return (
<View>
<MyComponent
onSelected={() => {
setParts(newParts)
}}
/>
</View>
)
}
basically, it is in the form of array de-structuring instead of object like you wrote above.
learn more: https://reactjs.org/docs/hooks-state.html

access `headerRight` inside a screen that is hosted by stack navigator

I have created a stack navigator:
import {createStackNavigator} from '#react-navigation/stack';
const TheStack = createStackNavigator();
Then, This is my navigator, it claimed component={LandingScreen}:
<TheStack.Navigator ...>
<TheStack.Screen
name="LandingScreen"
component={LandingScreen}
options={{
title: '',
headerLeft: null,
headerRight: () => (
<MyHeaderRightComponent />
),
}}
/>
<TheStack.Navigator>
As you can see above in options of the screen, there is headerRight, I have declared using MyHeaderRightComponent as headerRight so that it is shown on the right side of the header on screen.
Here is my LandingScreen.js :
import React, {useState} from 'react';
import {View, StyleSheet} from 'react-native';
const LandingScreen = ({navigation}) => {
// How can I access the `headerRight` component I have set above from here?
...
}
My question is how can I access the headerRight inside my LandingScreen.js? I know I can update or reset the headerRight by:
navigation.setOptions({headerRight:() => <NewHeaderRightComponent/>})
But now what I need is to access the previous already set component, not setting a new one. How to do that?
Edits to the answer as per the request received in comments. The answer is the same. This is just further demonstration on how to use it.
// The screen component where you want to pass the state.
const Screen = (props) => {
const [color, setColor] = useState("#CCCCCC");
const { navigation } = props //This is important or else UseEffect will be called each time any of the props change
useEffect(() => {
navigation.setParams({ color: color }); // Where its being passed.
}, [color, navigation]);
return (
<>
<Button onPress={() => setColor("#800000")} /> // Change the color state to Maroon
<Button onPress={() => setColor("#FED700")} /> // Change the color state to Gold
</>
)
}
Your Header Component:
const MyHeaderComponent = (props) {
return(
<View style={{ backgroundColor: props.bgColor }} />
)
}
Then you can retrieve this bit in headerRight. Like this:
headerRight:() => <MyHeaderComponent bgColor={route.params.color} />
Note: This method is valid for React Navigation v5. Version 4 has a getParams() function to retrieve the params, but it was dropped in Version 5.
Original Answer
You can create a useState hook in the screen and pass its value into your header component. So, when the header component updates the state, it can be accessed from within the screen where you have defined the state.
you can use setParams() function to set the params you want to use in the header component. Then, use route.params.nameofyourprop to get them in the headerComponent, where you can consume it.
This is to pass params from outside the header to inside of it.
headerRight:() => <MyHeaderRightComponent propname={route.params.propvalue} />
This to to set the Params from outside your header which you can access inside the headerRight component.
const [values, setValue] = useState()
navigation.setParams({propname: value})
This way you can pass state between the header and the screen.
You can also pass the setValue function of the useState in this manner, but it will throw a warning because functions are objects in Javascript and thus its not possible to index them... or something on those lines.

set React Native state to the Button title prop

I can't figure out how to update the state in my basic React Native application to equal whatever is in the title prop of the Button.
I've tried just setting the state to be {title} and that hasn't worked. I am using the useState hook so I don't think I should need to use "this.".
import React, {useState} from 'react';
import { View, Text, Button } from 'react-native';
const StarterForm = () => {
const [formStage, setFormStage] = useState(1)
const [feelings, setFeelings] = useState('')
console.log(feelings)
const updateFormStage = () => {
setFormStage(formStage + 1)
setFeelings({title})
}
switch (formStage) {
case 1:
return (
<View>
<Text>How are you?</Text>
<Button title="Excellent" onPress={updateFormStage}/>
</View>
)
case 2:
return (
<Text>This is the case of two</Text>
)
}
};
In the example, I expect console.log(feelings) to equal "Excellent" once the button has been pressed.
You can use ref for that, but I think the best way to solve your problem is store "Excellent" in a variable, and use onPress={() => updateFormStage(mVariable)}
One way would be setting reference for your defined button and after click on it, retrieve data from reference like this:
<Button ref={ref => { this.button = ref; }}
title="Excellent"
onPress={this.updateFormStage} />
You can access your title via button reference using this.button.title:
updateFormStage = () => {
console.log(this.button.title);
}