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

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

Related

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 ;
}

TypeError: Cannot read property 'map' of undefined ? Jest / React Native

I have an image slider component to display images from my Firebase database as follows ,
import React from 'react';
import Swiper from 'react-native-swiper';
import { View, StyleSheet, Image } from 'react-native'
const ImageSlider = ({images}) => {
return (
<Swiper autoplayTimeout={5}
style={styles.wrapper}
showsButtons={false}
loadMinimal={true}
showsPagination={true}
paginationStyle={styles.paginationStyle}
activeDotStyle={styles.activeDotStyle}
dotStyle={styles.dotStyle}
loop={true} autoplay={true}
>
{images.map((data, index) => {
return (
<View key={index} style={styles.slider}>
<Image style={styles.itemImage} source={{ uri: data }} />
</View>
)
})}
</Swiper>
)
}
For test above component I used follwing test file ,
import React from 'react';
import renderer from 'react-test-renderer';
import ImageSlider from '../../../src/components/imageSlider/ImageSlider';
test('renders correctly', () => {
const tree = renderer.create(<ImageSlider />).toJSON();
expect(tree).toMatchSnapshot();
});
When I'm run npm test command and after I got following error
TypeError: Cannot read property 'map' of undefined
Can anyone help me to slove this problem , Thank you
In your test, you're creating an ImageSlider without any parameters:
<ImageSlider />
In ImageSlider, you try to map the property images:
images.map( //etc
Because you didn't pass in an images parameter/property, images is undefined when you try to map it. To solve, this pass in value for images in your test:
<ImageSlider images={YOUR_TEST_IMAGES_DATA}/>
The other option is to redesign ImageSlider so that it fails gracefully if images is undefined. But, then there wouldn't be much a point in doing the test (unless the test was to see what happens if no parameter is passed in)

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.

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

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.

Using a class as props in a react native component

I'm learning react (coming from a native iOS/Swift background) and I'm a bit confused about something I can't get to work.
I have a component that accepts props, so I figured I would write a class to model those props:
class HeaderProps {
text: string;
constructor(headerText:string) {
this.text = headerText;
}
}
// Make a component
const Header = (props:HeaderProps) => {
const { textStyle, viewStyle } = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{props.text}</Text>
</View>
);
};
and I'm exporting from my component like so:
export {Header, HeaderProps};
I'm then importing it:
import {Header, HeaderProps} from './src/components/header';
// Create a component
const App = () => ( <Header headerText={ new HeaderProps('Album') } />);
No text is appearing in my component.
If I just pass a string through as props it works fine, can't think of any reason why sending a class through wouldn't work.
I'm using flow type to declare the types of my arguments, not sure if that might be causing any issues.
A point in the right direction would be much appreciated!