React Native Flatlist header re-rendering when scroll - reactjs

I am new to React Native and I am having problems with the header in a FlatList.
The header re-renders as soon as I start to scroll, this creates a flickering effect on the images I have in the header.
I have been searching for an answer everywhere but I have not find a posible solution.
¿how could I configure it to stop re-rendering the header when scrolling the list?
....
const Item = ({ title }) => {
return (
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
</View>
);
};
const listHeader = () => {
const categoriesButtons = categories.map(cat => (
<CategoryButton
text={cat.name}
icon={cat.code}
key={cat.code}
onPress={() => {
//#Todo logic to filter promotions accordingly with the category pressed
}}
/>
));
return (
<View>
<View style={styles.filtersContainer}>
<ImageBackground
source={images.bgShape}
style={{ width: '100%', height: 140 }}
resizeMode="stretch"
>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{categoriesButtons}
</ScrollView>
</ImageBackground>
</View>
<View style={styles.breadcrumbContainer}>
<Breadcrumbs navigation={navigation} stack={routes} />
</View>
<View style={styles.titleContainer}>
<Text style={sharedStyles.titleText} id="main-title">
¡{totalOfPromotions} promociones activas en Medellín!
</Text>
</View>
</View>
);
};
return (
<LinearGradient
colors={[Colors.BG_START, Colors.BG_END]}
style={styles.mainContainer}
>
{loading ? (
<ActivityIndicator size="large" color="#000000" />
) : (
<FlatList
data={promos}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={listHeader}
showsVerticalScrollIndicator={false}
onEndReached={showPromos}
onEndThreshold={0.7}
/>
)}
</LinearGradient>
);
};

listHeader() function is being called more than once because in Flatlist tag should be called as
<FlatList
data={promos}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={listHeader()}
showsVerticalScrollIndicator={false}
onEndReached={showPromos}
onEndThreshold={0.7}
/>
Use () while assigning ListHeaderComponent prop. By this way, function will be invoked only once.
Hope this help you. Enjoy coding!

From what I can see in the code you provided that you are defining the ListHeader component inside your other parent component which will redfine it on every render.
Moving it might outside the parent component might help.

You can fix your flickering issue by memoizing your ListHeaderComponent.
In your case just wrap your component with useMemo:
const listHeader = useMemo(() => {
...
})

Related

How do I scroll an entire screen that renders two flatLists?

I have two flatLists nested inside of a scrollView so I am able to scroll my entire page. However, I know that you are not supposed to nest flatLists in scrollViews for multiple reasons.
How can I render two flatLists while still being able to scroll throughout the entire page? The GIF at the bottom of the post is the desired behavior I want.
I created a snack post here as well as provided some example code below.
export default function App() {
return (
<View style={{ alignItems: 'center', marginTop: 100, flex: 1}}>
<FlatListB/>
<FlatListA/>
</View>
);
}
return (
<FlatList
data={newData}
renderItem={renderItem}
onEndReached={fetchMoreBars}
onEndReachedThreshold={0.1}
/>
);
return (
<FlatList
data={newData}
renderItem={renderItem}
onEndReached={fetchMoreBars}
onEndReachedThreshold={0.1}
horizontal={true}
/>
);
https://giphy.com/gifs/7V07FvYyn8ZG3nwVVU - This GIF was created by nesting FlatListB and FlatListA in a ScrollView
If you would like to prevent nested flatLists in scrollView, you may use ListHeaderComponent props in FlatList.
FlatListA.js:
<FlatList
data={newData}
renderItem={renderItem}
onEndReached={fetchMoreBars}
onEndReachedThreshold={0.1}
ListHeaderComponent={<FlatListB />}
/>
App.js
export default function App() {
return (
<FlatListA/>
);
}
This should work if 2 flatlists are not scrolling in same direction (Vertical / Horizontal).
try this code
const Data = [1,2,3,4,1,1,1,1,1,1,1,]
const Horizontal = () => {
const Data = [1,2,3,4,1,1,1]
return <FlatList
horizontal
data={Data} renderItem={() => {
return <View style={{height:100,width:100,backgroundColor:'pink',margin:2}}>
</View>
}}/>
}
const ListHeaderComponent = () => {
return <Horizontal/>
}
<View >
<FlatList
ListHeaderComponent={ListHeaderComponent}
data={Data} renderItem={() => {
return <View style={{height:100,width:100,backgroundColor:'white',margin:2}}>
</View>
}}/>
</View>

React Native cannot access the component props

I am still trying to finish my first React Native app. There is another thing for which I cannot find a solution.
My application has different Screens (HomeScreen, NewsScreen, etc). Each of these screens uses the component Screen:
const Screen = ({props, children}) => {
console.log("Screen props: " + props);
return (
<>
<OfflineNotice />
<Header {...props} />
<SafeAreaView style={styles.screen}>
<View style={styles.container}>{ children }</View>
</SafeAreaView>
</>
);
}
export default Screen;
Each Screen component has another Header component (there is a dropdown, by choosing another option it will change the displayed information on the screen)
Here is the Header component:
export default function Header (props) {
console.log("Header props: " + props.loadPredictions);
const {sport, setSport} = useContext(AppContext);
const logo = config[sport].logo;
return (
<View style={styles.container}>
<View style={styles.link}>
<TouchableOpacity
onPress={() => props.navigate("Settings")}
>
<MaterialCommunityIcons name="tune" size={32} />
</TouchableOpacity>
</View>
<View style={styles.logo}>
<Image source={logo} style={styles.image} />
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Menu
ref={this.setMenuRef}
button={<MaterialCommunityIcons name="chevron-down" size={24} onPress={this.showMenu} />}
>
<MenuItem onPress={() => {changeSport('soccer', setSport, props)}}>{i18n.t('predictions.p_1')}</MenuItem>
<MenuItem onPress={() => {changeSport('basketball', setSport, props)}}>{i18n.t('predictions.p_3')}</MenuItem>
<MenuItem onPress={() => {changeSport('volleyball', setSport, props)}}>{i18n.t('predictions.p_4')}</MenuItem>
<MenuItem onPress={() => {changeSport('hockey', setSport, props)}}>{i18n.t('predictions.p_5')}</MenuItem>
<MenuItem onPress={() => {changeSport('tennis', setSport, props)}}>{i18n.t('predictions.p_6')}</MenuItem>
</Menu>
</View>
</View>
</View>
);
};
And here is my HomeScreen:
export default function HomeScreen({ navigation }) {
...
useEffect( () => {
loadPredictions(params);
}, []);
const loadPredictions = async (params) => {
...
}
return (
<Screen navi={navigation} loadPredictions={loadPredictions}>
<View style={styles.activity}>
<ActivityIndicator animating={loading} size="large" />
</View>
<View style={styles.main}>
...
</View>
</Screen>
);
}
On the HomeScreen I am passing through the Screen component two props: navi and loadPredictions. Unfortunately, I cannot access them. I am getting the error undefined.
What am I doing wrong?
Simply because you made a distructuring the props in Screen Component, thus if you console log the props you will find it undefined, the best approach that you do this:
const Screen = ({children, ...restProps}) => {
console.log("Screen props: " + restProps);
return (
<>
<OfflineNotice />
<Header {...restProps} />
<SafeAreaView style={styles.screen}>
<View style={styles.container}>{ children }</View>
</SafeAreaView>
</>
);
}
export default Screen;
In this way we will be able to access to children prop and you pass the rest of the Screen Props to what Component you want.
I hope this answer can help you out.
change this
const Screen = ({props, children})
to
const Screen = (props)
and access children props.children

clickable Flat list items in React Native

I have a flat list that displays a list of item, I would like to make each item clickable.
My render function looks like this, and it works without any issue, except that it is not clickable
render() {
return (
<FlatList
data={formatData(data, numColumns)}
style={styles.container}
renderItem={this.renderItem}
numColumns={numColumns}
/>
);
}
I have tried to do something similar to this but it is giving me an error, any idea what's wrong with my code?
<FlatList
data={formatData(data, numColumns)}
style={styles.container}
renderItem={({this.renderItem}) => (
<TouchableHighlight
onPress={() => console.log("hello")}>
</TouchableHighlight>
)}
numColumns={numColumns}
/>
You can do like this :
renderItem with TouchableOpacity. Make sure to import it from react native.
import { TouchableOpacity } from "react-native";
import Icon from 'react-native-vector-icons/FontAwesome5';
...
...
render() {
return (
<FlatList
data={formatData(data, numColumns)}
style={styles.container}
renderItem={({item ,index}) => (
<TouchableOpacity
key={index.toString()}
onPress={() => console.log("clicked")}
>
<Icon name={item} color="red"/>
</TouchableOpacity>
)}
numColumns={numColumns}
key={numColumns.toString()} // if you want to use dynamic numColumns then you have to use key props
/>
);
}
First you need to import Touchable Opacity and the error you are getting , make sure data you are passing to flatlist is an array. So check this :
<FlatList
data={formatData(data, numColumns)} // this should be an array , or array of objects
renderItem={({item ,index}) => (
<TouchableOpacity
onPress={() => console.log("hello")}
>
<Text>Hi</Text>
</TouchableOpacity>
/>
Hope its clear
This is how you can iterate inside FlatList itself.
Working live https://snack.expo.io/#akhtarvahid/flatlist-simple
<FlatList
data={formatData(data, numColumns)}
style={styles.container}
renderItem={({item ,index}) => (
<TouchableOpacity
key={index.toString()}
onPress={() => console.log('Clicked item')}>
<Text>{'Hello'}</Text>
</TouchableOpacity>
)}
numColumns={numColumns}
keyExtractor={item=>item.id}
/>

Is it possible to prevent a modal from hiding if it's visibility prop rerenders?

I have a component with four different lists of items. An item is placed in a list depending on the date. When the user clicks a button to change the date of that item a modal appears with a date picker (the item's dateModalVisbility prop switches to true). This works as expected but then if an item's date change causes it to switch lists, the date picker modal disappears without the prop switching to false. Then when you click the button to reopen it switches to false and then after another press switches back to true and reopens. I believe this is caused by the rerender of the item when it switches into a different list.
I would like to force the modal to stay open even when it switches lists. I have tried making the different lists functions that I call before anything else renders but this did not solve the issue. I am out of ideas and unsure if this is even possible to do.
My flatlists (this.props.todos comes from redux):
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (moment().isSame(item.date, 'day')) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Tomorrow</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (
moment()
.add(1, 'day')
.isSame(item.date, 'day')
) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Upcoming</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (
moment()
.add(1, 'day')
.isBefore(item.date, 'day')
) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Sometime</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (moment().isAfter(item.date, 'day') || item.date === null) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
The todoItem component:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<ItemSwipeRow item={todoItem} completeItem={this.props.deleteTodo}>
<TouchableHighlight
onPress={() => this.props.toggleItemMenu(todoItem)}
underlayColor={null}>
<ListItem
containerStyle={styles.todoItem}
contentContainerStyle={styles.contentStyle}
title={todoItem.text}
titleStyle={{ color: '#FCEFEF', fontSize: 16 }}
rightElement={todoItem.date ? this.renderDate.bind(this)() : null}
/>
</TouchableHighlight>
</ItemSwipeRow>
{todoItem.itemMenuToggled ? <ItemMenuBar item={todoItem} /> : null}
{this.props.reminderToggleActive && todoItem.date ? (
<ReminderToggleButtons item={todoItem} />
) : null}
<NotesModal item={todoItem} />
{todoItem.dateModalVisible ? <DatePickerModal item={todoItem} /> : null}
</View> //this line above is responsible for displaying the date picker modal
);
}
}
And the DatePickerModal:
class DatePickerModal extends Component {
render() {
return (
<Modal transparent animationType="fade" visible>
<View style={styles.containerStyle}>
<View style={styles.modalContainer}>
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
<View style={{ padding: 5 }}>
<Feather
name="x-square"
size={35}
color={'#db5461'}
onPress={() => this.props.toggleDateModal(this.props.item)}
/>
</View>
</View>
<View style={{ padding: 5 }}>
{Platform.OS === 'ios' ? (
<IosDatePicker item={this.props.item} />
) : (
<AndroidDatePicker />
)}
</View>
</View>
</View>
</Modal>
);
}
}
I can provide the actions and button that opens the item but I don't think that's where the issue is coming from. The button/actions are doing their job right.
I would re-arrange the way you are using the modal. Instead of each component having a modal add one modal to the top level component and then use props to set the items that was selected. Somthing like this:
export default class TopComponent extends React.Component {
this.state = {
selectedItem: null,
modalvisible: false,
}
render = () => {
return (
<FlatList renderItem={({item, key} => (<MyItem onItemSelected={item => this.setState({ selectedItem: item, modalVisible: true}))} />} />
<Modal visible={this.state.modalVisible}>
<Text>this.state.selectedItem</Text>
</Modal>
}
}
Here is a quick codesandbox to show some more details: https://codesandbox.io/s/react-native-6518p

weird results from flatlist in react native with touchable opacity

I have a FlatList in a React Native project. The touchable opacity does register as the view loads, for each row, the console prints selected item.name.. (so, without being touched) however, touching the item does not do anything.
render() {
return(
<FlatList
data={stores.databaseStore.sites.slice()}
keyExtractor={ (item, index) => item.id}
numColumns={1}
extraData={stores.databaseStore.isLoadingSites}
onRefresh={() => this.onRefresh()}
refreshing={stores.databaseStore.isLoadingSites}
renderItem={({item}) => this._renderFlatListItem(item)}
ItemSeparatorComponent={this._renderSeparator}
ListHeaderComponent={this._renderHeader}
ListFooterComponent={this._renderFooter}
/>
)
}
_renderFlatListItem(item) {
return (
<View style={styles.row}>
<TouchableOpacity onPress={this._showSiteDetails(site)}>
<View style={styles.cellLeft} >
<PivotCircle site={item}/>
</View>
</TouchableOpacity>
</View>
)
}
_showSiteDetails(site){
console.log(`selected ${site.name}`);
}
found it with the help of some gents on slack
onPress={this._showSiteDetails(site)}
should be
onPress={ () => this._showSiteDetails(item)}

Resources