React Native APIs - why use destructuring with just one parameter? - javascript

I'm studying JavaScript, React Native, and React Navigation. I have just learned about destructuring. It's great, except I don't see the point when there is only one parameter. For example, why not just write
function HomeScreen( navigation ) { /* same code as below */
instead of
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
}
Thanks for any insights

Typically functional components will take a props object as an agrument. props might have any number of key value pairs. For example, you might have props.navigation. But writing function HomeScreen( navigation ) is not the same as writing function HomeScreen({ navigation }). Writing { navigation } instead of props in the argument desctuctures the value from its parent object. If you wanted to avoid destructuring, you would write it like this:
function HomeScreen( props ) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => props.navigation.navigate('Details')}
/>
</View>
);
}
Again, a react functional component expects to recieve the props object as the argument. We usually call it props, but it could be called anything. This next block is identical, we're just naming props something else:
function HomeScreen( swissArmyKnife ) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => swissArmyKnife .navigation.navigate('Details')}
/>
</View>
);
}
For just one parameter, you're right, it's probably not necessary to destructure. Its just a shorthand. But when you have a props object with many many parameters, and you want to pull only certain values from it, it makes more sense. like lets say your props object had parameters navigation, trains, busses, airplanes, bikes, skateboards, and you just want to use a few in your component:
function HomeScreen( {busses, bikes} ) {
// do some stuff here with busses and bikes, don't worry about the whole props object
}

The first argument passed to a React component is the props object made up of all the attributes in the JSX.
<HomeScreen navigation={...} />
It's going to be an object no matter what.
Destructuring is the conventional way to show which props you care about in your component. Some linters will take advantage of that and alert discrepancies between the JSX and the component.

Related

I don't understand the reaction of my onPress

I created a short test to get an explanation of how my code reacts.
Basically I want to call a function only when I press my button.
No problem, the code below works.
// Components/Test.js
import React, { useState } from "react";
import { View, StyleSheet, TextInput, Button } from "react-native";
function Test(props) {
const [text, setText] = useState("");
const myFunction = () => {
console.log("test");
};
return (
<View style={styles.container}>
<TextInput
style={{ borderWidth: 1, width: "70%" }}
onChangeText={(text) => setText(text)}
/>
<Button title="Button" onPress={myFunction} />
</View>
);
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
export default Test;
As I'm learning Javascript at the same time, I thought I'd put parentheses after my function. Technically, if I want to pass parameters to my function, I thought I should do it like this.
<Button title="Button" onPress={myFunction()} />
And now with this change, my "myFunction" is called systematically if I modify the text in TextInput.
Can someone explain what is going on? Thank you !
You should use arrow function
<Button title="Button" onPress={() => myFunction('parameter')} />
if you just call like
<Button title="Button" onPress={myFunction()} /> // its calling function directly
Its call the function directly
Arrow Functions:
We all love them, but should be cautious when using them with React. Arrow functions are great, they let us quickly create new javascript function short hand. The consequence of this is exactly that, it creates a new function every time it is executed. For the most part in Javascript this isn’t a real issue. When it comes to the React render method this can quickly become an issue.
render(){
return (
<View>
<TouchableOpacity onPress={() => console.log('Hello!')}>
Click Me
</TouchableOpacity>
</View>
)
}
So what could be the harm here, the button is simply just logging out a string on press. But in fact, when React executes this render method it will always see a new function. The arrow function returns a new function every time. This causes React to think something has changed in our view, when in fact nothing has.
logHello(){
console.log('Hello!');
}
render(){
return (
<View>
<TouchableOpacity onPress={this.logHello}>
Click Me
</TouchableOpacity>
</View>
)
}
Everytime you change the text, the state of the component also changes becuase, you've used onChangeText={(text) => setText(text)}, this causes your component to re-render.
During re-render, the following line is also executed. In this line you are calling myFunction.
<Button title="Button" onPress={myFunction()} />
In short, changing the text causes state update, which in turn causes a re-render, and during that re-render myFunction is called.

How to pass different event handlers for the same event?

I have a react-native component which is rendering 2 Icons for marking a favorite and writing a comment as shown below:
function RenderDish(props) {
const dish = props.dish;
if (dish != null) {
return (
<Card featuredTitle={dish.name} image={{ uri: baseUrl + dish.image }}>
<Text style={{ margin: 10 }}>
{dish.description}
</Text>
<View style={{ display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "center" }}>
<Icon raised reverse name={props.favorite ? 'heart' : 'heart-o'} type='font-awesome' color='#f50'
onPress={() => props.favorite ? console.log('Aleady Favorite') : props.onFavorite()} />
<Icon raised reverse name='pencil' type='font-awesome' color='#3b5998'
onPress={() => props.onComment()} />
</View>
</Card>
);
}
else {
return (<View></View>);
}}
I am calling this functional component from the outer component as shown below:
<RenderDish dish={this.props.dishes.dishes[+dishId]}
favorite={this.props.favorites.some(el => el === dishId)}
onFavorite={() => this.markFavorite(dishId)}
onComment={() => this.toggleModal()} />
I have already implemented the toggleModal() and the markFavorite() methods and everything is working as expected but my question is: Is there any other way of passing 2 or more different event handlers through a single prop ? For eg. Is there any way to say something like: <RenderDish dish={xyz} onPress={()=> handler1 AND handler2}. Or is there any elegant alternative to what I have done(if I had 5 buttons I would need 5 props :( ) ?
You can have your handlers in a single function and call the function in onPress.
or just
onPress={()=> {handler1, handler2}}
Try something like below. Pass the handler methods as an object in a props.
function Button(props) {
return (
<div>
<button type="button" onClick={props.onPress.handler1} >Handler1</button>
<button type="button" onClick={props.onPress.handler2} >Handler2</button>
</div>
);
}
class Home extends Component {
handler1() {
console.log('handler 1');
}
handler2() {
console.log('handler 2');
}
render() {
return (
<div>
<Button onPress={{ handler1: this.handler1, handler2: this.handler2 }} />
</div>
)
}
}
The way that you’ve done it is probably the most common way and perfectly acceptable.
It’s not generally a problem if there are only a few handlers (and they aren’t being passed deeply).
You could bundle them into an object and single prop, but I don’t typically see it done that way and I don’t think there’s a strong benefit.
You can also pass in multiple props as a single spread object without having to bundle them into a single prop.
If you start getting a more handlers than you’re comfortable with, then that would probably be a good time to look at how you’re handling your state management. You may be better off with a reducer and dispatching actions at that point.
Update
A few other notes about your code:
You can destructure props for easier use.
You do not need a separate closing tag for View if there is no content.
You do not have to make new arrow functions for event handlers if all you are doing is calling another arrow function. You can just set it to the first arrow function.
Consider a ternary to allow an implicit return.
function RenderDish({dish, favorite, onFavorite, onComment}) =>
dish
? <Card featuredTitle={dish.name} image={{ uri: baseUrl + dish.image }}>
<Text style={{ margin: 10 }}>
{dish.description}
</Text>
<View style={{ display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "center" }}>
<Icon raised reverse name={favorite ? 'heart' : 'heart-o'} type='font-awesome' color='#f50'
onPress={() => favorite ? console.log('Aleady Favorite') : onFavorite()} />
<Icon raised reverse name='pencil' type='font-awesome' color='#3b5998'
onPress={onComment} />
</View>
</Card>
: <View/>
I would focus more on these items, rather than bundling those two functions.

How to render Container Classes conditionally

firstly, this is what is given to me from designer http://www.giphy.com/gifs/hSRrqF5ObsbXH27V09
Basically, there is a category which is passed from previous screen. and with some ui interactions, i need to render this screen again and again. the flow is like that: you select a category, if it has subCategories, let user select one of those subCategories before rendering input components. i can make it work with if and else clauses but i feel that this is some how not best practice at all. I just need an advice from experieced developer(i am reletively new to react native.)
So before writing any code with native way, i just want to ask it here so maybe i can learn more about it.
Here is my Screen:
<NewAdHoc
contentText={'Kategori Secimi'}
onBackPress={this.handleBackPress}
showContentText={false}
>
<View style={styles.container}>
{currentCategory !== null
? (
<View style={{ ...styles.flatListContainer, paddingLeft: undefined, paddingRight: undefined }}>
<View style={{ marginHorizontal: 20 }}>
<ListViewItem
categoryName={currentCategory.categoryName}
iconName={currentCategory.categoryIcon}
showBorder={false}
key={currentCategory.categoryId}
categoryId={currentCategory.categoryId}
inNewAdScreen={false}
/>
</View>
<Seperator
backgroundColor={colors.SEPERATOR_BCK}
text={'Ilan Turu'}
style={{ paddingHorizontal: 20 }}
/>
{
currentCategory.subCategories.map((subc) => (
<View style={{ marginHorizontal: 20 }}>
<SubCategoryItem
text={subc.subCategoryName}
key={subc.subCategoryId}
showBorder={true}
/>
</View>
))
}
</View>
) : null}
</View>
</NewAdHoc>
right now, i am rendering a category, a <Seperator/> between category and subcategories, and subcategories. what i want is that, when user click on one of the subCategories, i will change the state to isSubCategorySelected = true, selectedSubCategory= subCategoryId and then need to render the whole screen like in gif i provided above.
the For those who came here for answer:
What i did is basically divide and conquer paradigm. I firstly divide my sitution into two main state.
RenderInitialForm(),
renderAfterSubCategorySelected(),
those two rendering functions handle the whole process. when a TouchableOpacity is clicked, i setState with two variables: isSubCategorySelected = true, selectedSubCategory = subCategory
and in my main render() function:
render() {
const { currentCategory, isSubCategorySelected, selectedSubCategory } = this.state
return (
<NewAdHoc
contentText={'Kategori Secimi'}
onBackPress={this.handleBackPress}
showContentText={false}
>
<View style={styles.container}>
{currentCategory !== null
? isSubCategorySelected
? this.renderAfterSubCategorySelected()
: this.renderInitialForm()
: null}
</View>
</NewAdHoc>
);
}
if you have any suggestion with my solution, please feel free to contact me.

React Native FlatList doesn't render

I'm trying to add a searchable list to my React Native app, but encounter a problem whilst trying to render the list itself. The error is the old "You likely forgot to export your component from the file its defined in, or you might have mixed up default and named imports". I'm sure this may be my issue, but after reading several variations of the issue online, I can't seem to figure out where the issue is.
I've tried changing every and any used import in the fashion of listing them one by one, using and removing the brackets. I tried reinstalling react-native-elements, and checked my dependencies for correct versions. Also tried rendering list without data.
List component:
Liste.js
import { View, Text, FlatList } from "react-native";
import {List, ListItem } from "react-native-elements"
class Liste extends Component {
constructor(props) {
super(props);
...
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<List>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
/>
)}
/>
</List>
</View>
);
}
}
export default Liste;
I expected the list to render at all, it doesnt.
First, you need to remove the List component, because the react-native-elements does not contain it.
The second thing that you need to do is to remove alignItems: "center", justifyContent: "center" from the View component.
Also, in the FlatList component, the property avatar is wrong. You have to choose between: leftAvatar and rightAvatar.
You comonent should like look this:
<View style={{ flex: 1 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={item.title}
subtitle={item.body}
leftAvatar={{
source: item.thumbnail && { uri: item.thumbnail },
}}
/>
)}
/>
</View>
Here is a working demo.

TouchableOpacity onPress hangs inside ListView

I have this ListView UI that started to run slowly once I added the rowData parameter inside the onPress event handler of TouchableOpacity. Once the TouchableOpacity is pressed, it stays pressed for 15 seconds and then again runs smoothly.
There seems to be some collision, because I use rowData also in the renderRow event handler of ListView three lines above.
Am I right and how to solve this problem?
<ListView
dataSource={this.state.dataSource}
keyboardShouldPersistTaps={true}
renderRow={(rowData) =>
<TouchableOpacity
onPress={(rowData) => {
console.log(rowData);//ON THIS LINE IT HANGS 15s
}}
>
<Text>{rowData}</Text>
</TouchableOpacity>
}
automaticallyAdjustContentInsets={false}
/>
I'd be really interested in the explanation of what makes this such an expensive operation in javascript, but the problem is that you're passing rowData as an argument to your onPress function, when rowData is already declared in the upper scope (renderRow). So yes, just like you said, there's a collision.
Effectively, the value of rowData is being redefined by onPress, since the onPress function receives the touch event as an argument. (You'll notice that the data being logged isn't actually your original row data, but a touch event object).
You can fix this by simply renaming the first argument of your onPress function. e.g.
<TouchableOpacity
onPress={(evt) => {
console.log(rowData); //now it doesn't hang
}}
>
Can we put header with Webview ? like
render() { // eslint-disable-line class-methods-use-this
return (
<Container theme={theme} style={{ backgroundColor: theme.defaultBackgroundColor }}>
<Header style={{ justifyContent: 'flex-start', paddingTop: (Platform.OS === 'ios') ? 23 : 9 }}>
<Button transparent onPress={() => this.popRoute()} >
<Icon name="ios-arrow-round-back-outline" style={{ fontSize: 30, lineHeight: 32, paddingRight: 10 }} />
Find Stores
</Button>
</Header>
<WebView
ref={WEBVIEW_REF}
automaticallyAdjustContentInsets={false}
source={{uri: this.state.url}}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate="normal"
onNavigationStateChange={this.onNavigationStateChange}
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
startInLoadingState={true}
scalesPageToFit={this.state.scalesPageToFit}
/>
</Container>
);

Categories

Resources