Unable to receive props to dump component - react-native

I tried all possible ways to get a simple parameter on file MembershipCard.js. My Home component Home.js simply passes props to MembershipList.js where I have done minor Array operations and iterate it to prepare a list. Each item from the list is then pass on to third file MembershipCard.js. I'm getting membership object in this file and able to prepare a card list at Home page. On Home page I have to show a side line whereas I don't want this side line on other pages (which are also accessing MembershipCard.js) hence I'm trying to send a variable on which I will conditionally show side line.
But after so many try out I'm still receiving undefined
This is my React component - Home.js
render () {
return (
<Surface>
<GreetingCard profile={this.props.profile.Profile}/>
<MembershipList props={this.props}/>
</Surface>
)
}
MembershipList.js - this contain only few functions
renderMembershipCard = (membership, i, props, sideLine = true) => {
return (
<TouchableOpacity key={i} style={styles.membership} onPress={() => props.navigation.navigate('Item', { title: membership.gym_name })}>
{/* <MembershipCard {...{membership, sideLine }}/> */}
<MembershipCard {...membership} sideLine={sideLine}/>
</TouchableOpacity>
)
}
const MembershipList = (props) => {
let membership = props.props.profile.Membership
let listArray = [];
Object.keys(membership).forEach(key => listArray.push(this.renderMembershipCard(membership[key], key, props.props)));
return (
<View>
<Text style={styles.ListTitle}>Active Membership ({listArray.length})</Text>
{listArray}
</View>
);
}
MembershipCard.js - this file is part of my presentation layer. It only return a Card design.
const MembershipCard = ({membership,sideLine}) => {
console.log('sideLine', sideLine); // showing undefined
console.log('membership', membership);
return (
<Card>
<Text style={styles.gymTitleText}>{membership.name}</Text>
... JSX code
</Card>
)

Make the following changes to your code and it should work. Change props.props seems to be incorrect way of passing props. Use spread operator for passing all props to children in correct manner.
<MembershipList {...this.props}/>
const MembershipList = (props) => {
let membership = props.profile.Membership
let listArray = [];
Object.keys(membership).forEach(key => listArray.push(this.renderMembershipCard(membership[key], key, props)));
return (
<View>
<Text style={styles.ListTitle}>Active Membership ({listArray.length})</Text>
{listArray}
</View>
)}
<MembershipCard membership={membership} sideLine={sideLine}/>

I solved it using simple trik.
Instead of calling it as a component -
<MembershipCard {...membership} sideLine={sideLine}/>
call it as a simple JS function using curly braces {} -
{ MembershipCard (membership, sideLine) }
This way I can easily pass as many parameters and can easily access all those in called function.

Related

Onclick button, state doesn't change

I'm having some troubles understanding the code.
Basically if I do this:
<Text onPress={() => console.log('123')} >123</Text>
,
and I click on the text, it logs me 123 each click.
But I'm doing a dialer app. Basically having a component Tile (representing a single number, also with secondary option (but that will be dealt with later)).
So as I'm including my (currently only single one) Tile in App.js, I want onPress event to call function that changes state of currently dialed number. So if user clicks 3 times on '1', I want the final string to be 111.
The thing is, I can see that the handleNumber() function is called once after running code and never again after clicking on the number, therefore never changing state, not logging anything.
App.js Tile implementation:
const [getNumber, setNumber] = useState();
const handleNumber = (newChar) => {
console.log(newChar);
setNumber(newChar);
}
<Tile onPress={() => handleNumber('1')} style={styles.tile} firstChar={'1'} secondChar={'✉'}/>
Tile.js:
const Tile = (props) => {
return (
<TouchableOpacity onPress={props.onPress()}>
<View>
<Text style={styles.mainChar}>{props.firstChar}</Text>
{props.secondChar ? <Text style={styles.secondChar}>{props.secondChar}</Text> : null}
</View>
</TouchableOpacity>
)
}
One more thing:
As you can see in App.js::handleNumber(),
the implementation is currently wrong, because the final number will always be just the new single number, instead of appending it to the end of string.
But if I wanted to do smth like setNumber(getNumber + newChar), it would give Maximum update depth exceeded error.
The thing here is the way that you're passing the onPress prop to TouchableOpacity. See, you're calling the props.onPress function instead of just passing it to the component, which causes the function to be executed when the component renders (or rerenders).
You should be fine just by using <TouchableOpacity onPress={props.onPress}> and then setNumber(getNumber + newChar) will be fine as well.
As a side note, you could initialize your App.js state with the '1' string that you wish to be your initial value (const [getNumber, setNumber] = useState('1')), and then the Tile component can receive getNumber directly.

what is the difference between passing an object with a variable and a lone variable to a react native component?

My lack of success in this problem may be due to a lack of proper terminology when Googling it but nonetheless I am completely stumped. I am passing an onPress function to a custom component in react native. When I pass it by itself as:
export const AddMorePlants = ( onPress ) => {
return (
<TouchableHighlight
onPress={onPress}>
.
.
.
}
I get a this2.props.onPress is not a function error but when I have the exact same code except with the onPress passed within curly braces:
export const AddMorePlants = ({ onPress }) => {
return (
<TouchableHighlight
onPress={onPress}>
.
.
.
}
It Works!
Why does the second one work and not the first?
Sorry for a kind of basic question I just have been really Googling and cant figure it out. Thanks in advance and I can provide any more info if needed.
A functional component in React only has one parameter. The props. You can read more about it here
So what your first attempt at passing the onPress function actually looks like is:
export const AddMorePlants = (props) => {
return (
<TouchableHighlight onPress={props}/>
);
}
When the TouchableOpacity tries to execute the method, it hits the is not a function error because props is an object.
When you do:
export const AddMorePlants = ({onPress}) => {
return (
<TouchableHighlight onPress={onPress}/>
);
}
what you are doing is something called destructuring assignment and it's equivalent of doing:
export const AddMorePlants = (props) => {
const {onPress} = props;
return (
<TouchableHighlight onPress={onPress}/>
);
}
By putting the brackets inside the parentheses you are just doing a shorthand version of this destructuring assignment that we have mentioned.
Here's another version that would also work:
export const AddMorePlants = (props) => {
return (
<TouchableHighlight onPress={props.onPress}/>
);
}
As you can see there are many ways to access an object's property.
The important part is to remember that the props object is the only parameter passed into a functional component.
I hope this helps you understand what's going on there.
In the first function you need to pass only one param i.e onPress to your component while in second you are destructuring assignment so you are doing something like onPress = this.props.onPress and passing an object of params.

React Native Context dispatch delete current screen(item) causes render error

In my app, I have a list view of recipes and also a detail view of just one recipe.
I would like, in the detail view, to delete the recipe.
export default function RecipeDetails({ route, navigation }) {
const { recipes, dispatch } = useContext(RecipeContext);
const id = route.params.item.id;
const currentRecipe = recipes.find((r) => r.id === id);
return (
<ScrollView>
<TouchableOpacity
onPress={() => {
dispatch({ type: "remove", obj: currentRecipe });
navigation.goBack();
}}
>
<FontAwesomeIcon icon={faTrash} size={20} />
</TouchableOpacity>
<Image
style={styles.img}
source={images[currentRecipe.image]}
resizeMode="cover"
/>
My goal is to dispatch the remove action and then go back to the list view.
However, right now, after the dispatch of remove fires, I get an error saying "currentRecipe.image" is undefined.
My question is, why doesn't the navigation direct back to the last screen(list view) so this undefined error won't occur ?
This happens because your component wants to re-render after the recipe is removed but before you navigate back -- it receives new props where the recipe is already gone and so currentRecipe is undefined and accessing currentRecipe.image throws an error.
What you can do is prevent accessing currentRecipe if it does not exist.
One way to do this is by returning null or a placeholder - this is not the most elegant solution:
const currentRecipe = recipes.find((r) => r.id === id);
if (!currentRecipe) return null;
Alternatively, you can copy your recipe from context to local state for reading:
const [currentRecipe] = useState(recipes.find((r) => r.id === id));
This will mean currentRecipe does not change after you have initially loaded it - not when you remove it, but also not when user edits it (if they have that option).

How can I refactor this small piece of code so that I don't call a function inside render

I read on this page that you should not create functions inside the render method. One way around this is to bind.
My constructor looks like this:
constructor(props) {
super(props);
this.idCheckAlert = this.idCheckAlert.bind(this);
}
The function I created:
idCheckAlert = (idForAPI) => () => {
//does some stuff with the idForApi
}
Inside my render:
<TouchableOpacity
style={styles.inputButton}
onPress={() => {
const idForAPI = this.userId;
this.idCheckAlert(idForAPI);
}}
>
My current refactor looks like this:
<TouchableOpacity
style={styles.inputButton}
onPress={this.idCheckAlert(this.userId)}
>
However, I would like to instead create a variable inside onPress, but when I do I get an error message that says unexpected token. Is there a way for me to create a variable inside onPress, without having to create a function?
Is not a good practice create the variable inside onPress, just like you said, but you can do it after the render
const userId = 1;
return (
<TouchableOpacity
style={styles.inputButton}
onPress={this.idCheckAlert(userId)}
>
);

React native pass property for async

So I got an array with a ton of fields that are bound to <Text>
Now I also have a button in here. What I want to do is onclick I want to go to a function onSubmit which also takes the value session.gameId and puts it in asyncstorage.
{this.state.sessions.map((session, index) => {
return (
<View style={styles.cardContainer}>
<Text style={styles.title} >{session.title}</Text>
<Text style={styles.text}>Organisator: {session.organizer}</Text>
<Text style={styles.text}>Hoofd Thema: {session.mainTheme}</Text>
<Text style={styles.text}>Aantal Deelnemers: {session.numberParticipants}</Text>
<Text style={styles.text}>Game Id: {session.gameId}</Text>
<TouchableOpacity style={styles.buttonContainer} >
<Text style={styles.buttonText} onPress={this.onSubmit} value={session.gameId} >
Selecteer
</Text>
</TouchableOpacity>
But I have no idea how my onsubmit can handle both an event to change page after storing the value into asyncstorage
Please help
Not sure if I'm understanding your question correctly, but if you're trying to store the value of session.gameId into AsyncStorage and then change the page, your function may look something like this:
changePage() {// functionality to navigate to another page}
/*
* have onSubmit be an async function so you use the 'await' keyword
* await forces an asynchronous line of code to wait until the operations is done before moving forward
*/
async onSubmit(gameId) {
try {
await AsyncStorage.setItem('#store:key', gameId)
} catch(error) {
// handle error
}
// however you are changing page, handle it here
// this code wont run until gameId has been stored in async storage
this.changePage()
}
You would also need to pass the gameId to the function to actually call it now:
onPress={() => this.onSubmit(session.gameId)}
Take a look at how async functions can make your life easier :)
I'm answering this assuming that when you say your onSubmit triggers "an event to change page", you mean that it navigates to another screen.
If so, you seem to be asking for something like this:
onSubmit = async gameId => {
try {
await AsyncStorage.setItem('gameId', gameId)
// Success: navigate away here
this.props.goToMyOtherScreen()
} catch {
// Handle error
}
}
To get gameId into your submit handler, you could use an inline anonymous function:
<Text
style={styles.buttonText}
onPress={() => this.onSubmit(session.gameId)}>