Will memoization prevent my React component from rerendering? - reactjs

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?

Related

How to Limit render element Flat List and add more content in React Native

I want like that flatlist render only 5 checkbox items and then when I click to +5 more button it will show 5 more checkbox list.In this all checkbox list appearing but i want only five
Please help me how to achieve that
Thanks in advance
const renderResourceList = renderData => {
return (
<FlatList
data={renderData}
initialNumToRender={5}
maxToRenderPerBatch={5}
pagingEnabled={true}
nestedScrollEnabled={true}
renderItem={({item}) => (
<View style={styles.card}>
<TouchableOpacity
onPress={() => {
if(resourceTypeArray.includes(item)){
setResourceTypeArray(currentList => {
return currentList.filter(items => items !== item);
});
}
else{
setResourceTypeArray(currentList => [
...currentList,
item
]);
}
onSetResourceType(item);
}}
style={styles.modalBtn}>
<Icon
name={
resourceTypeArray.includes(item) ? 'checkbox-marked' : 'checkbox-blank-outline'
}
size={18}
color="#353C3C"
style={{bottom: -1}}
/>
<View style={styles.textWrapper}>
<Text style={styles.modalText}>{item.charAt(0)}
{item.toLowerCase().slice(1).replace(/_/g, ' ')}</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
);
};
I tried but this not working
I used a package called flatlist-react to handle this for me. A decent tutorial for this can be found here.
With this package, you can directly specify and limit the items rendered with the limit prop. Here is an example:
<FlatList
limit="2,-2"
list={people}
renderItem={Person}
/>
If you keep track of the limit prop variables using state, you can dynamically change these values when you click 5+ more in order to render whatever part of your data you would like.

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>

pass database data to another screen in react native

Now I wanted to implement a function that when I click on a list of data from mysql server on my react native app, it will move to another screen and pass the detail of the data into it here is the code that I have implement
const GetJobDetail=useCallback= (user,job, jobid, machinecode, startTime) =>{
navigation.navigate('Jobview', {
UserId : user,
Job : job,
JobId:jobid,
MachineCode : machinecode,
StartTime : startTime
},[]);
}
return (
<View style={{ flex: 1, padding: 24 }}>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<Button title={item.job} style={styles.rowViewContainer} onPress={()=>navigation.navigate(GetJobDetail(item.user,item.job,item.jobid,item.machinecode,item.startTime))}/>
)}
/>
)}
</View>
);
I have already successful fetch the data into my react native apps, the data look like
the data is only show the job of the user instead of showing all detail if I enter userid='1111' then it will only show the job for userid='1111'. but now i wish to implement that if I clicked the job in this list it will move to next screen and send all the details to that screen using route.params. I have tried using callback function but it gave me error "useCallback" is read-only.
I also founded some source from other site but it is in class component the code will look like this.
i am sure that this first is the function that pass the value to another screen
GetStudentIDFunction=(student_id,student_name, student_class, student_phone_number, student_email)=>{
this.props.navigation.navigate('Third', {
ID : student_id,
NAME : student_name,
CLASS : student_class,
PHONE_NUMBER : student_phone_number,
EMAIL : student_email
});
}
and this one is calling the function and pass the data to another screen
renderRow={ (rowData) => <Text style={styles.rowViewContainer}
onPress={this.GetStudentIDFunction.bind(
this, rowData.student_id,
rowData.student_name,
rowData.student_class,
rowData.student_phone_number,
rowData.student_email
)} >
{rowData.student_name}
</Text> }
pls help me to point out what is my mistake or error. Thanks
update:
after tried multiple time finally it works with this code. On the first page it get the data
const GetDetail=(id,user,job,jobid,machinecode,startTime)=>{
navigation.navigate('JobView',{Id:id,UserId:user,Job:job,JobId:jobid,MachineCode:machinecode,StartTime:startTime});
}
return (
<View style={{ flex: 1, padding: 24,backgroundColor:'cyan' }}>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<Text style={styles.rowViewContainer} onPress={()=>{GetDetail(item.id,item.user,item.job,item.jobid,item.machinecode,item.startTime)}}>{item.job}</Text>
)}
/>
)}
</View>
);
}
On this screen it pass the data from previous screen by using route.params&&route.params.variable\\variable=your own variable that carry the data from th eprevious screen
useEffect(()=>{
setCode(route.params && route.params.MachineCode)
setUserid(route.params && route.params.UserId)
setJob(route.params && route.params.Job)
setJid(route.params && route.params.JobId)
setStarttime(route.params && route.params.StartTime)
setId(route.params && route.params.Id)
},[]
)
Hope this will help you guys when faced the same problem :)
Store your data in some useState after fetching. You can send data to another screen/component in two ways:
You can pass data as props or use a callBack if those screens/components are in parent-child relationship.
Parent to child: Pass as props
<FlatList
data={customerData}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
keyExtractor={(item, index) => index}
renderItem={({item, index}) => (
<KYC_ListCell
itemdata={customerData[index]}
onItemClick={() => {
props.updateCustomerInformation(customerData[index]);
navigation.navigate('Customer360Info');
}}
/>
Using a call back to pass data from child to parent
//Parent
<HeaderView
username={'HelpDesk'}
showHeaderWithSearch
placeholderText={'Customer ID'}
showRefreshIcon
onMenuClick={() => {
setModalVisible(true);
}}
onRefreshClicked={() => {
getComplaintsListOnPageLoad();
}}
onSearchClicked={text => {
setSearch(text);
getSearchedItem(text);
}}
/>
//child
<Search
value={search}
placeholderText={props.placeholderText}
onChangeText={text => setSearch(text)}
onClearText={() => setSearch('')}
onSearchPressed={() => props.onSearchClicked(search)}
/>
You can pass data while navigating as route param

How to cancel all subscriptions and asynchronous tasks in a useEffect cleanup function

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
The page here includes a selectable FlatList. However if I select another element too fast (before the whole flatList is loaded), the error above will appear. I'm not sure which function I should put in useEffect and what return statements I should put so that it cleans up (or let the previous flatList stop loading).
The error specifically pointed out the Item function, as shown in the code below (unrelated codes skipped)
function ChampSelect({navigation, route}) {
...
const Item = ({ item, onPress, weight, color}) => (
<TouchableOpacity onPress={onPress}>
<Image
source={require('../pictures/champions/' + item + '.png')}
style= {[styles.image, color]}
/>
<Text style={[styles.title, weight]}>
{item}
</Text>
</TouchableOpacity>
)
const renderItem = ({ item }) => {
const fontWeight = item === selectedChamp ? 'bold' :'normal';
const borderColor = item === selectedChamp ? '#6BDB5A' :'black'
return (
<Item
item={item}
onPress={() => setSelectedChamp(item)}
weight={{ fontWeight }}
color = {{borderColor}}
/>
)
}
...
return (
...
<View style={{marginLeft: 48, marginTop: 40}}>
<FlatList
ListHeaderComponent={renderHeader}
numColumns={3}
horizontal={false}
data={data}
extraData={selectedChamp}
renderItem={renderItem}
keyExtractor={item => item}
/>
</View>
</View>
)
}
Any help would be appreciated!

Resources