Rendering specific component in React Native - reactjs

I used the ScrollView's onMomentumScrollEnd handler to determine the current page based on the contentOffset in the recyclerlistview Component.
const [currentPage, setCurrentPage] = useState(0);
const onScrollEnd = (e) => {
let contentOffset = e.nativeEvent.contentOffset;
let viewSize = e.nativeEvent.layoutMeasurement;
let pageNum = Math.floor(contentOffset.x / viewSize.width);
setCurrentPage(pageNum);
};
return (
<View style={{ flex: 1 }}>
<RecyclerListView
rowRenderer={({ item }, index) => <Page item={item} id={index} />}
dataProvider={list}
layoutProvider={layoutProvider}
onMomentumScrollEnd={onScrollEnd}
isHorizontal
pagingEnabled
/>
<View style={{ flex: 0.1, backgroundColor:"gold" }}>
<Text>{`current page: ${currentPage}`}</Text>
</View>
</View>
);
I want to display the current Page inside a Text component, but when currentPage state changes in onMomentumScrollEnd handler the whole app is re-render. I need to re-render only the Text Component, any suggestion for that.

I find a solution. the easiest way is to create a class component and put the displayed component in it, then I reference it and change the current page with ref hook.
class DisplayCurrentPage extends React.Component {
contractor(prop){
super(prop)
state:{currentPage:0}
}
updateCurrentPage = (index) => {this.setState({CurrentPage: index})}
render(){
return(
<View style={{ flex: 0.1, backgroundColor:"gold" }}>
<Text>{`current page: ${this.state.currentPage}`}</Text>
</View>
);
}
};
const reference = createRef();
const onScrollEnd = (e) => {
let contentOffset = e.nativeEvent.contentOffset;
let viewSize = e.nativeEvent.layoutMeasurement;
let pageNum = Math.floor(contentOffset.x / viewSize.width);
reference.current.updateCurrentPage(pageNum);
};
return (
<View style={{ flex: 1 }}>
<RecyclerListView
rowRenderer={({ item }, index) => <Page item={item} id={index} />}
dataProvider={list}
layoutProvider={layoutProvider}
onMomentumScrollEnd={onScrollEnd}
isHorizontal
pagingEnabled
/>
<DisplayCurrentPage ref={reference} />
</View>
);
When the page is changed, only DisplayCurrentPage is re-render.

You can use React.memo which is an alternative to shouldComponentUpdate for functional components. It tells React when to re-render the component based on prev and next props.
const Item = React.memo(({ item }) => {
return (
<View>
........
</View>
);
});

Related

Passing state from function to component

My entire goal was to navigate from a screen while changing states in the screen I am navigating to. I have successfully done that in a minimal working example, however in my overall project, the screen I am navigating to needs to be passed the state through a couple levels.
I have two examples. In the first example(You must run the examples in IOS or android, you can see what I need to achieve, everything works as it should. You can move from screen3 to the home page and the states change along with the slider button moving.
In the second example, you can see right off the bat I have an error due to my attempt at passing states the same way I do in the original example however there is one more level I need to pass through in this example. You can see by removing line 39 in this demo, it removes the error so obviously I am not passing states correctly. I need to pass states from Home to Top3 to Slider
Here is example 1 and here is example 2 while I have also provided some code below that highlights the differences where the error occurs in the two examples.
Any insight at all is appreciated more than you know! Thank you.
Example1 -> you can see I directly render the slider button which causes zero issues.
const Home = ({ route }) => {
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Slider
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'|| false}
/>
</View>
</View>
);
}`
Example2 -> You can see I render Slider in a file called Top3, I am struggling to pass these states from Home to Top3 to Slider.
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
goToMap()
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Top3
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'|| false}
/>
</View>
</View>
);
}
Top3
export default class Top3 extends React.Component {
goToMap = () => {
this.props.renderMap();
};
goToList = () => {
this.props.renderList();
};
render() {
return (
<View>
<Slider renderMap={this.goToMap.bind(this)}
renderList={this.goToList.bind(this)}
active={active}/>
</View>
);
}
}
from your examples, I think you are not extracting active from props properly.
here is the demo working code your example2 code https://snack.expo.dev/4atEkpGVo
here is the sample code for component Top3
export default class Top3 extends React.Component {
goToMap = () => {
this.props.renderMap();
};
goToList = () => {
this.props.renderList();
};
render() {
const {active=false} = this.props;
return (
<View>
<Slider renderMap={this.goToMap.bind(this)}
renderList={this.goToList.bind(this)}
active={active}/>
</View>
);
}
}
if you want to share states between multiple screens, then you might want to use global stores like react context api or redux instead of passing states to each screen that would be simple

Error when mapping a Props from A to B Component Undefined is not an object (evaluating 'item.cate.map)

Hello and Good Day Everyone. I am newbie to react world and I am practicing and building an Food App UI and now, I am trying to map the category props which is a state in my Home category and I pass it through props now I am trying to map it in my product component.
Home Component Code
const [categories, setCategories] = useState(categoryData)
const [selectedCategory, setSelectedCategory] = useState(null)
const [restaurants, setRestaurants] = useState(restaurantData)
const [currentLocation, setCurrentLocation] = useState(initialCurrentLocation)
function onSelectedCategory(Category){
let restaurantList = restaurantData.filter(a => a.categories.includes
(Category.id))
setRestaurants(restaurantList)
setSelectedCategory(Category)
}
function getCategoryNameById(id) {
let kategory = categories.filter(a => a.id == id)
if(kategory.length > 0){
return kategory[0].name
}else{
return ""
}
}
return (
<HomeComponentContext.Provider value={{
selectedCategory
}}>
<SafeAreaView>
<Top name={currentLocation.streetName}/>
<Categories
item={categories}
funct={onSelectedCategory}
selectcat={selectedCategory}
/>
<Product
item={restaurants}
func={getCategoryNameById}
cate = {categories}
/>
</SafeAreaView>
</HomeComponentContext.Provider>
)
Product Component
{
item.cate.map((item) => {
return(
<View
key={item}
style={{
flexDirection:'row'
}}
>
<Text
style={{
...FONTS.body3
}}
>
{()=>props.catfunc(item)}
</Text>
<Text style={{
...FONTS.h3,
}}> .
</Text>
</View>
)
})
}
in your Product component you are looping over item.cate.map which is throwing an error because item is undefined there.
You are passing categories in cate props, so to map over categories you need to do props.cate.map instead of item.cate.map.
And I recommend to use meaningful variable names, item is very generic and will create confusion and sometimes very annoying bugs.

Checking for viewable items in a Flatlist and passing a prop if the item being rendered id matches a viewable items id

So basically what I said in the title, I am simply trying to change a prop that im passing to the component of Post if the item currently being rendered is in the viewport.
I am getting double the output like its firing twice, and its not even correct.
im comparing a key to the id (same thing) and if the 'activeVideo !== item.id' (video id's) are the same
the video should play, because i pass 'False' to the 'paused' property.
question is why am i getting double the output and why are both videos being paused when one of them clearly shouldnt>?
Need help fast, its for a school project.
Home.js
const [activeVideo, setActiveVideo] = useState(null);
const onViewRef = React.useRef((viewableItems)=> {
setActiveVideo(viewableItems.changed[0].key)
})
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 75 })
return (
<View>
<FlatList
data={posts}
showsVerticalScrollIndicator={false}
snapToInterval={Dimensions.get('window').height - 24}
snapToAlignment={'start'}
decelerationRate={'fast'}
onViewableItemsChanged={onViewRef.current}
viewabilityConfig={viewConfigRef.current}
renderItem={({item}) => <Post
post={item}
paused={activeVideo !== item.id}
/>}
/>
</View>
)}
Post.js
const Post = (props) => {
const [post, setPost] = useState(props.post);
const [paused, setPaused] = useState(props.paused);
console.log(props.paused)
const onPlayPausePress = () => {
setPaused(!paused);
};
const onPlayPausePress2 = () => {
setPaused(!paused);
};
return (
<View style={styles.container}>
<TouchableWithoutFeedback>
<Image
source={{uri: post.poster}}
style={styles.video2}
/>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={onPlayPausePress}>
<Video
source={post.postUrl}
style={styles.video}
onError={(e)=> console.log(e)}
resizeMode={'cover'}
repeat
paused={paused}
/>
</TouchableWithoutFeedback>
</View>
)}

Exposing state props from functional component to navigationOptions function

I have a component that renders the input field, I want to pass the data to the next page when user clicks on "next" button in the header. What is the best practice for doing so? How do I expose this into Page.navigationOptions?
Or is it best to just set up redux for these types of things?
const Page = () => {
const [desc, getDesc] = useState('');
return (
<View style={styles.inputFieldDescContainer}>
<TextInput
multiline
placeholder='Write a description...'
onChangeText={(text) => getDesc(text)}
value={desc}
/>
</View>
);
};
// How do I pass desc properties down into navigationOptions?
Page.navigationOptions = (navData) => {
return {
headerTitle: 'Page,
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Next'
onPress={() => {
navData.navigation.navigate('NextPage', {data: navData});
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
/* NextPage.js */
const NextPage = (props) => {
console.log('enter props data', props.navigation.getParam('data'));
console.log('enter props navigation', props.navigation);
const [valueText, setValueText] = useState();
return (
<View>
<TextInput onChangeText={(text) => setValueText(text)} value={valueText}/>
<TouchableOpacity><Text>Create your workout</Text></TouchableOpacity>
</View>
);
;}
Sharing state and props between component and options is possible in React Navigation 5 https://blog.expo.io/announcing-react-navigation-5-0-bd9e5d45569e
In React Navigation 4, you can use params to store the value to be able to share it:
const Page = ({ navigation }) => {
const desc = navigation.getParam('description', '');
return (
<View style={styles.inputFieldDescContainer}>
<TextInput
multiline
placeholder='Write a description...'
onChangeText={(text) => navigation.setParams({ description: text )}
value={desc}
/>
</View>
);
}

Improving the performance of rendering hundreds of components in React

I am rendering a list in React Native which currently has about 900 list items. I'm mapping through the list and rendering one component for each item. It currently takes about 3 seconds for React to do this which is unacceptable - I would like it to be near instant. Props are passed to the list item component from the redux store and the list items are nested inside the React Native ScrollView Component.
How can I can I increase the performance of rendering these components so there is not such a huge lag?
Here is my Contacts component:
class Contacts extends Component {
renderRegisteredUsers = (contacts) => {
return contacts.items.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.user.address_book_name}
surname={''}
phoneNumber={contact.user.phone}
isRegisteredUser={true}
ccId={contact.user.id}
/>
))
}
renderContacts = (contacts) => {
if (contacts) {
return contacts.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.firstName}
surname={contact.surname}
phoneNumber={contact.phoneNumber}
isRegisteredUser={false}
/>
))
} else {
return (
<>
<Loader />
</>
)
}
}
render() {
return (
<>
<ScrollView>
<Text style={{ fontSize: 22 }}>
Your Contacts Using Fleeting
</Text>
{this.renderRegisteredUsers(this.props.user.registeredContacts)}
<Text style={{ fontSize: 22 }}>
Phone Contacts
</Text>
{this.renderContacts(this.props.user.parsedContacts)}
</ScrollView>
</>
)
}
}
const mapStateToProps = (state) => {
const { user } = state;
return { user }
};
export default connect(mapStateToProps)(Contacts);
And my ContactListItem component:
class ContactListItem extends Component {
constructor(props) {
super(props)
}
handleOnClick = () => {
this.props.calleeId(this.props.ccId)
Actions.TimeInput();
}
render() {
return (
<View style={{ margin: 20, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Text>
{this.props.firstName + ' ' + this.props.surname + ' ' + this.props.phoneNumber}
</Text>
<Icon name="adduser" size={40} color="green" style={{ alignSelf: "flex-end" }} onPress={this.handleOnClick} />
</View>
)
}
}
const mapDispatchToProps = {
calleeId,
};
export default connect(null, mapDispatchToProps)(ContactListItem);
Thanks in advance.
1- You can use PureComponent instead of Component. PureComponent re-renders only when its props change and not re-rendering on each parent re-render. More Information: https://reactjs.org/docs/react-api.html#reactpurecomponent
2- Use unique keys when you're mapping on your items.
3- You can use FlatList instead of ScrollView. It supports Scroll Loading. You can set a number of initial numbers and render the others on scroll. More Information: https://facebook.github.io/react-native/docs/flatlist
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'first' }, { key: 'second'}, ...+1000];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Look into using pagination instead- https://www.npmjs.com/package/react-paginate, https://www.npmjs.com/package/react-native-pagination. If your contacts are coming from an api request, you will have to look into paginating the requests too.
import React, { Component } from 'react';
import {AppRegistry,StyleSheet,View,FlatList,} from 'react-native';
import ContactItem from './Pages/widgets/ContactItem'; // https://github.com/garrettmac/react-native-pagination/blob/master/ReactNativePaginationExample/Pages/widgets/ContactItem.js
import faker from 'faker';//assuming you have this.
import _ from 'lodash';
import Pagination,{Icon,Dot} from 'react-native-pagination';//{Icon,Dot} also available
export default class ReactNativePaginationExample extends Component {
constructor(props){
super(props);
this.state = {
items: this.props.contacts,
};
}
//create each list item
_renderItem = ({item}) => {
return (<ContactItem index={item.id}
onPressItem={this.onPressItem.bind(this)}
name={item.name}
avatar={item.avatar}
description={item.email}
tag={item.group}
createTagColor
/>)
};
//pressed an item
onPressItem = (item) => console.log("onPressItem:item ",item);
_keyExtractor = (item, index) => item.id;
onViewableItemsChanged = ({ viewableItems, changed }) =>this.setState({viewableItems})
render() {
return (
<View style={[s.container]}>
<FlatList
data={this.state.items}
ref={r=>this.refs=r}//create refrence point to enable scrolling
keyExtractor={this._keyExtractor}//map your keys to whatever unique ids the have (mine is a "id" prop)
renderItem={this._renderItem}//render each item
onViewableItemsChanged={this.onViewableItemsChanged.bind(this)}//need this
/>
<Pagination
// dotThemeLight //<--use with backgroundColor:"grey"
listRef={this.refs}//to allow React Native Pagination to scroll to item when clicked (so add "ref={r=>this.refs=r}" to your list)
paginationVisibleItems={this.state.viewableItems}//needs to track what the user sees
paginationItems={this.state.items}//pass the same list as data
paginationItemPadSize={3} //num of items to pad above and below your visable items
/>
</View>
)
}
};
const s = StyleSheet.create({
container: {
flex: 1,
// backgroundColor:"grey",//<-- use with "dotThemeLight"
},
});
AppRegistry.registerComponent('ReactNativePaginationExample', () => App);

Resources