React Native: How does usage of nested components affect performance? - reactjs

Let's say I have a FlatList component that renders items whose counts can be anywhere between 10 to 100+.
The official doc on FlatList optimization says "Try to avoid a lot of logic and nesting in your list items." I want to understand better what this actually means.
For the item components, I have a couple of options (Examples are simplified, the actual components are much more complicated obviously):
Option 1: Using a nested structure, e.g. :
const ItemComponent = (props) => {
const ChildComponent1 = (props) => {
return (
<View>
<Image source={props.image}/>
</View>
)
}
const ChildComponent2 = () => {
return (
<View>
<Text>This is Child Component.</Text>
</View>
)
}
return (
<View>
<Text>This is {props.name} </Text>
<ChildComponent1 image={props.image}/>
<ChildComponent2/>
</View>
)
}
Option 2: Using a non-nested structure, e.g. :
const ItemComponent = (props) => {
return (
<View>
<Text>This is {props.name} </Text>
<View>
<Image source={props.image}/>
</View>
<View>
<Text>This is Child Component.</Text>
</View>
</View>
)
}
Am I actually better off in terms of performance if I use option 2?
If so, how much performance gain will I have and why? I'd like to understand deeper, but cannot find a resources that explains the rationale in detail.
Thanks!

Related

How to use the same ref between child/parent component

I am trying to control the carousel in the child component from the parent component.
I have used forward ref on the child component but its not working. Where am I going wrong?
Parent:
const CoachingCarousel = ({}) => {
const carouselRef = useRef<Lottie>(null);
const renderItem = ({item}: any) => {
return (
<View style={styles.renderItemContainer}>
{item.icon}
<Text style={[styles.titletext, spacing.gbMt7]} variant="titleLarge">
{item.title}
</Text>
<Text style={[styles.subtitleText, spacing.gbMt4]} variant="bodyMedium">
{item.text}
</Text>
<Text
style={[styles.next]}
variant="bodyLarge"
onPress={() =>
carouselRef?.current?.goToSlide(
totalSlides !== item.key
? item.key
: () => {
setCoachingScreenCompleted('CoachingScreenCompleted', true),
console.log('Go to homepage');
},
)
}>
{totalSlides !== item.key ? 'Next tbc' : 'Done tbc'}
</Text>
</View>
);
};
return (
<AppCarousel slides={slides} renderItem={renderItem} ref={carouselRef} />
);
};
Child:
const AppCarousel = React.forwardRef(
({style, slides, renderItem}: props, ref) => {
return (
<View style={[styles.container, style]}>
<AppIntroSlider
ref={ref}
renderItem={renderItem}
data={slides}
/>
</View>
);
},
);
Here a is React rule,
1. Do not declare components within other components, this will lead to very weird behaviors in React.
2. Also you cannot share ref between two components at the same time whether they are parent/child or siblings.
Answer
Also from what I am seeing, you are using a ref to keep track of the current slide. ref does not rerender in React so you will not see your changes. Try using useState.
useRef is for non rerendering values or to keep track of DOM nodes which is not necessary in your case.
I found this tutorial to be quite well. One critic would be the over complicated use of React.children.map and React.children.clone although they have their use cases.

React Native: Pressable items within a ScrollView causing automatic snap to the top

Hi I'm working on a mobile application in react native. Its a simple app that lets you search for ingredients in a search bar.
I have a small bug which I can't seem to fix regarding the scrollview and setstate. I have a function that displays the search list under my search bar. It is an absolutely positioned element. The bug occurs when I try to scroll down on the scrollview and press on one of the items, instead of the scrollview remaining in place it scrolls directly to the top. Ideally I would like the scrollview to remain in place once the user has pressed on a item. I feel like it has something to do with how I'm calling setstate. The function is as follows:
const [userIngredients, setUserIngredients] = useState([]);
const OutputSearchList = () => {
const clickIngredient = (i) => {
if (!userIngredients.includes(i)) {
setUserIngredients((temp) => [...temp, i]);
} else {
const temp = userIngredients.filter((item) => item !== i);
setUserIngredients(temp);
}
};
return ingredients.map((item) => {
return (
<TouchableOpacity
style={styles.searchBarListItem}
onPress={() => clickIngredient(item)}
key={item}
>
<View style={styles.searchBarListContentChecked}>
<Text style={styles.searchBarListText}>{item}</Text>
<Ionicons
style={styles.searchBarListAdd}
name="checkmark-circle-outline"
size={28}
color="#000000"
/>
</View>
</TouchableOpacity>
);
});
};
and the function is called like so:
<ScrollView>
{ingredients?.length > 0 ? (
<View>
<OutputSearchList />
</View>
) : searchText?.length > 0 ? (
<View>
<Text>No ingredients found</Text>
</View>
) : (
<View>
<Text>Search for an ingredient (e.g. Eggs, Beef, Rice)</Text>
</View>
)}
</ScrollView>

Will memoization prevent my React component from rerendering?

I'm struggling to understand what causes my component to rerender as there is no state present here. Code to focus on is Flatlist as this is React Native but this is about React and not specifically RN. As you can see inside Flatlist I render renderPost. I know that renderPost, aka reusable component PostListItem rerenders because this console.log you can see inside flatlist gives me repeated keys, and even console logging postsSortedByDate shows me that they render few times. Because of this, I even have a warning child list keys must be unique while I definitely gave flatlist unique IDs from my array, that warning is because of these rerendering. I guess I need to use Memo somewhere?:
const FeedScreen: React.FC<FeedScreenProps> = ({ navigation }) => {
const { postsSortedByDate } = checkPostsContext();
const navigateToCreatePostScreen = async () => {
navigation.navigate("CreatePostScreen");
};
const renderPost: ListRenderItem<IPost> = ({ item }) => (
<PostListItem post={item} key={item.uniquePostID} />
);
return (
<ScreenContainer>
<SafeAreaView>
<Header
containerStyle={styles.containerStyleHeader}
headerTitle="Factory X Feed"
rightIcon="add-photo-alternate"
onPressRightIcon={navigateToCreatePostScreen}
/>
</SafeAreaView>
{postsSortedByDate.length === 0 ? (
<View style={styles.noPostsContainer}>
<Text>
No posts exist at the moment, please create one by tapping on the
image icon in the header. In the meantime, as indicated by the
spinner below, we will continue to try to fetch posts in case some
do exist but are just loading slowly.
</Text>
<ActivityIndicator size="large" color="#0000ff" />
</View>
) : (
<FlatList
data={postsSortedByDate}
renderItem={renderPost}
keyExtractor={(item, index) => {
console.log("IDS:", item.uniquePostID.toString());
//HERE IS THE ISSUE as here I see same keys rendering //multiple times, and I checked and I did not save posts in a wrong way //for few of them to have the same key somehow. Its now about that. //Even logging posts themselves(here) shows me they re render.
return item.uniquePostID.toString();
}}
/>
)}
</ScreenContainer>
);
};
My PostListItem component:
const PostListItem: React.FC<PostItemProps> = ({ post }) => {
return (
<View style={styles.container}>
{post.postImage ? (
<Lightbox
renderContent={() => {
return (
<Image
source={{ uri: post.postImage }}
resizeMode="cover"
style={[
styles.imageInLightbox,
{
width: width,
height: height,
},
]}
/>
);
}}
>
<Image
source={{ uri: post.postImage }}
resizeMode="contain"
style={styles.image}
/>
</Lightbox>
) : null}
<React.Fragment>
{post.postDescription ? (
<Text style={styles.textStyle}>
{hashtagFormatter(post.postDescription)}
</Text>
) : (
<Text style={styles.textStyle}>{defaultPostDescription}</Text>
)}
</React.Fragment>
</View>
);
So why is PostListItem, I mean renderPost rerendering and how to fix it?

InfiniteScroll using ScrollView - React Native

I have a list using the ScrollView of react native itself. Basically, I build a list dynamically through an API return.
async fetchData(userSearch) {
const {route} = this.props;
const {params} = route;
const {type} = params;
this.setState({
loading: true,
});
const responseProcedures = await scheduleResource.getProcedures(userSearch);
this.setState({
procedures: responseProcedures.data,
loading: false,
});
}
<ScrollView
onScroll={(event) => this.shouldLoadMoreContent(event)}
>
{procedures.map(procedure => (
<ArrowBox key={procedure.id} onPress={() => RootNavigation.navigate('ProcedureDetails', {procedure})}>
<Text bold style={styles.ProcedureTitle}>
{procedure.name}
</Text>
{!!procedure.synonyms.length && (
<>
<Text bold style={styles.ProcedureSynonymTitle}>
SinĂ´nimos
</Text>
<View style={styles.ProcedureSynonymOptionsContainer}>
{procedure.synonyms.map(synonym => <Text style={styles.ProcedureSynonymOption} key={synonym}>{synonym}</Text>)}
</View>
</>
)}
</ArrowBox>
))}
</ScrollView>
The problem is that I load the entire return from the API and it slows down.
I would like to know how to dynamically load the content and make new calls in the api, when I reach the end of the page.
Api allows me to place offset and limit.
Could someone give me some example?
Thanks!!!!!
Basically the ScrollView is not designed to handle dynamic data, the correct component that is designed to perform this function is called Flatlist. It works almost exactly like ScrollView but it is faster and will only render components that are shown on the screen.
Please import Flatlist from React Native like this...
//At the top of your file, please import FlatList together with all the modules that you want
import { FlatList, Text, View } from "react-native";
Then replace the entire ScrollView in your code with a Flatlist like this:
<FlatList
keyExtractor={(procedure) => procedure.id}
data={this.state.procedures}
renderItem={(procedure) => {
return (
<ArrowBox
key={procedure.id}
onPress={() =>
RootNavigation.navigate("ProcedureDetails", {
procedure })}>
<Text bold style={styles.ProcedureTitle}>
{procedure.name}
</Text>
{!!procedure.synonyms.length && (
<>
<Text bold style={styles.ProcedureSynonymTitle}>
SinĂ´nimos
</Text>
<View
style={styles.ProcedureSynonymOptionsContainer}>
{procedure.synonyms.map((synonym) => (
<Text
style={styles.ProcedureSynonymOption}
key={synonym}>
{synonym}
</Text>
))}
</View>
</>
)}
</ArrowBox>
);
}}
></FlatList>;

How to conditionally use React Native elements

I need to use different wrappers to render the screen based on a certain condition.
Example, I have a situation where I need to wrap the content in a view
<View>
<Text> </Text>
<View> </View>
{ and other elements here }
</View>
But in a different scenario, I need the View to be Content from nativeBase.
if useContent variable is true, then render everything using Content as a wrapper, else use View.
How would I best do that ?
Use a ternary operator to help you conditionally render.
render() {
const useContent = true;
return useContent ? (
<Content>
<Text></Text>
<View></View>
</Content>
) : (
<View>
<Text></Text>
<View></View>
</View>
)
}
It may also be better to extract these two pieces into their own components. Just so your component file doesn't become too large and maintainability starts to be a concern. Edit: If you want to keep the children of the component the same and just the wrapping element to change create a second component that renders children:
class WrappingElement extends Component {
render() {
const { children, useContent } = this.props;
return useContent ? (
<Content>{children}</Content>
) : (
<View>{children}</View>
)
}
}
class Foo extends Component {
render() {
<WrappingElement useContent={false}>
<Text>Hello World!</Text>
<View></View>
</WrappingElement>
}
}
<View>
booleanCondition1() && <Text> </Text>
booleanCondition2() && <View> </View>
booleanConditionN() && { and other elements here }
</View>
<View>
{condition == true ?
(
<Text> </Text>
<View> </View>
)
:
(
{ and other elements here }
)
}
</View>

Resources