React Native Flatlist element onPress not fired until List rendering is complete - javascript

I have a FlatList that receives (immutable) data of max. 50 elements and it renders in each list item Svg using react-native-svg.
Parts of the graphics are wrapped with a Pressable component for selecting the element.
Now the problem is, that I can't select any of the elements, until the FlatList went through all 50 items.
What I don't get is, that the offscreen items aren't even rendered, it's just the containers. Once it's all rendered, I can click the elements, the ripple effect shows and the event is fired.
Specs:
Expo # 46.0.0
React Native # 0.69.6
React # 18.0.0
Running with android via expo start --no-dev --minify then open in Expo Go
Reproduction:
import React, { useEffect, useState } from 'react'
import { FlatList } from 'react-native'
import { Foo } from '/path/to/Foo'
import { Bar } from '/path/to/Bar'
export const Overview = props => {
const [data, setData] = useState(null)
// 1. fetching data
useEffect(() => {
// load data from api
const loaded = [{ id: 0, type: 'foo' }, { id: 1, type: 'bar' }] // make a list of ~50 here
setData(loaded)
}, [])
if (!data?.length) {
return null
}
// 2. render list item
const onPressed = () => console.debug('pressed')
const renderListItem = ({ index, item }) => {
if (item.type === 'foo') {
return (<Foo key={`foo-${index}`} onPressed={onPressed} />)
}
if (item.type === 'bar') {
return (<Foo key={`bar-${index}`} onPressed={onPressed} />)
}
return null
}
// at this point data exists but will not be changed anymore
// so theoretically there should be no re-render
return (
<FlatList
data={data}
renderItem={renderListItem}
inverted={true}
decelerationRate="fast"
disableIntervalMomentum={true}
removeClippedSubviews={true}
persistentScrollbar={true}
keyExtractor={flatListKeyExtractor}
initialNumToRender={10}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={100}
getItemLayout={flatListGetItemLayout}
/>
)
}
}
// optimized functions
const flatListKeyExtractor = (item) => item.id
const flatListGetItemLayout = (data, index) => {
const entry = data[index]
const length = entry && ['foo', 'bar'].includes(entry.type)
? 110
: 59
return { length, offset: length * index, index }
}
Svg component, only Foo is shown, since Bar is structurally similar and the issue affects both:
import React from 'react'
import Svg, { G, Circle } from 'react-native-svg'
const radius = 25
const size = radius * 2
// this is a very simplified example,
// rendering a pressable circle
const FooSvg = props => {
return (
<Pressable
android_ripple={rippleConfig}
pressRetentionOffset={0}
hitSlop={0}
onPress={props.onPress}
>
<Svg
style={props.style}
width={size}
height={size}
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
>
<G>
<Circle
cx='50%'
cy='50%'
stroke='black'
strokeWidth='2'
r={radius}
fill='red'
/>
</G>
</Svg>
</Pressable>
)
}
const rippleConfig = {
radius: 50,
borderless: true,
color: '#00ff00'
}
// pure component
export const Foo = React.memo(FooSvg)
The rendering performance itself is quite good, however I can't understand, why I need to wait up to two seconds, until I can press the circles, allthough they have already been rendered.
Any help is greatly appreciated.
Edit
When scrolling the list very fast, I get:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. {"contentLength": 4740, "dt": 4156, "prevDt": 5142}
However, the Components are already memoized (PureComponent) and not very complex. There must be another issue.
Hardware
I cross tested with an iPad and there is none if the issues described. It seems to only occur on Android.

Please ignore grammatical mistakes.
This is the issue with FlatList. Flat list is not good for rendering a larger list at one like contact list. Flatlist is only good for getting data from API in church's like Facebook do. get 10 element from API and. then in the next call get 10 more.
To render. a larger number of items like contact list (more than 1000) or something like this please use https://bolan9999.github.io/react-native-largelist/#/en/
import React, {useRef, useState} from 'react';
import {
Image,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import {LargeList} from 'react-native-largelist-v3';
import Modal from 'react-native-modal';
import {widthPercentageToDP as wp} from 'react-native-responsive-screen';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import fonts from '../constants/fonts';
import {moderateScale} from '../constants/scaling';
import colors from '../constants/theme';
import countries from '../Data/larger_countries.json';
const CountrySelectionModal = ({visible, setDefaultCountry, setVisible}) => {
const pressable = useRef(true);
const [country_data, setCountryData] = useState(countries);
const [search_text, setSearchText] = useState('');
const onScrollStart = () => {
if (pressable.current) {
pressable.current = false;
}
};
const onScrollEnd = () => {
if (!pressable.current) {
setTimeout(() => {
pressable.current = true;
}, 100);
}
};
const _renderHeader = () => {
return (
<View style={styles.headermainView}>
<View style={styles.headerTextBg}>
<Text style={styles.headerTitle}>Select your country</Text>
</View>
<View style={styles.headerInputBg}>
<TouchableOpacity
onPress={() => searchcountry(search_text)}
style={styles.headericonBg}>
<FontAwesome
name="search"
size={moderateScale(20)}
color={colors.textColor}
/>
</TouchableOpacity>
<TextInput
placeholder="Select country by name"
value={search_text}
placeholderTextColor={colors.textColor}
style={styles.headerTextInput}
onChangeText={text => searchcountry(text)}
/>
</View>
</View>
);
};
const _renderEmpty = () => {
return (
<View
style={{
height: moderateScale(50),
backgroundColor: colors.white,
flex: 1,
justifyContent: 'center',
}}>
<Text style={styles.notFoundText}>No Result Found</Text>
</View>
);
};
const _renderItem = ({section: section, row: row}) => {
const country = country_data[section].items[row];
return (
<TouchableOpacity
activeOpacity={0.95}
onPress={() => {
setDefaultCountry(country),
setSearchText(''),
setCountryData(countries),
setVisible(false);
}}
style={styles.renderItemMainView}>
<View style={styles.FlagNameView}>
<Image
source={{
uri: `https://zoobiapps.com/country_flags/${country.code.toLowerCase()}.png`,
}}
style={styles.imgView}
/>
<Text numberOfLines={1} ellipsizeMode="tail" style={styles.text}>
{country.name}
</Text>
</View>
<Text style={{...styles.text, marginRight: wp(5), textAlign: 'right'}}>
(+{country.callingCode})
</Text>
</TouchableOpacity>
);
};
const searchcountry = text => {
setSearchText(text);
const items = countries[0].items.filter(row => {
const result = `${row.code}${row.name.toUpperCase()}`;
const txt = text.toUpperCase();
return result.indexOf(txt) > -1;
});
setCountryData([{header: 'countries', items: items}]);
};
return (
<Modal
style={styles.modalStyle}
animationIn={'slideInUp'}
animationOut={'slideOutDown'}
animationInTiming={1000}
backdropOpacity={0.3}
animationOutTiming={700}
hideModalContentWhileAnimating={true}
backdropTransitionInTiming={500}
backdropTransitionOutTiming={700}
useNativeDriver={true}
isVisible={visible}
onBackdropPress={() => {
setVisible(false);
}}
onBackButtonPress={() => {
setVisible(false);
}}>
<LargeList
showsHorizontalScrollIndicator={false}
style={{flex: 1, padding: moderateScale(10)}}
onMomentumScrollBegin={onScrollStart}
onMomentumScrollEnd={onScrollEnd}
contentStyle={{backgroundColor: '#fff'}}
showsVerticalScrollIndicator={false}
heightForIndexPath={() => moderateScale(49)}
renderIndexPath={_renderItem}
data={country_data}
bounces={false}
renderEmpty={_renderEmpty}
renderHeader={_renderHeader}
headerStickyEnabled={true}
initialContentOffset={{x: 0, y: 600}}
/>
</Modal>
);
};
export default CountrySelectionModal;
const styles = StyleSheet.create({
modalStyle: {
margin: moderateScale(15),
borderRadius: moderateScale(10),
overflow: 'hidden',
backgroundColor: '#fff',
marginVertical: moderateScale(60),
justifyContent: 'center',
},
headermainView: {
height: moderateScale(105),
backgroundColor: '#fff',
},
headerTextBg: {
height: moderateScale(50),
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
headerTitle: {
textAlign: 'center',
fontFamily: fonts.Bold,
fontSize: moderateScale(16),
color: colors.textColor,
textAlignVertical: 'center',
},
headerInputBg: {
height: moderateScale(40),
borderRadius: moderateScale(30),
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: moderateScale(10),
backgroundColor: colors.inputbgColor,
flexDirection: 'row',
},
headericonBg: {
backgroundColor: colors.inputbgColor,
alignItems: 'center',
justifyContent: 'center',
width: moderateScale(40),
height: moderateScale(40),
},
headerTextInput: {
backgroundColor: colors.inputbgColor,
height: moderateScale(30),
flex: 1,
paddingTop: 0,
includeFontPadding: false,
fontFamily: fonts.Medium,
color: colors.textColor,
paddingBottom: 0,
paddingHorizontal: 0,
},
notFoundText: {
fontFamily: fonts.Medium,
textAlign: 'center',
fontSize: moderateScale(14),
textAlignVertical: 'center',
color: colors.textColor,
},
renderItemMainView: {
backgroundColor: colors.white,
flexDirection: 'row',
alignSelf: 'center',
height: moderateScale(43),
alignItems: 'center',
justifyContent: 'space-between',
width: wp(100) - moderateScale(30),
},
FlagNameView: {
flexDirection: 'row',
justifyContent: 'center',
paddingLeft: moderateScale(12),
alignItems: 'center',
},
imgView: {
height: moderateScale(30),
width: moderateScale(30),
marginRight: moderateScale(10),
borderRadius: moderateScale(30),
},
text: {
fontSize: moderateScale(13),
color: colors.textColor,
marginLeft: 1,
fontFamily: fonts.Medium,
},
});

Related

Resulting in a blank screen when listing data with Firebase(Firestore) in React Native

Data listing process with Firebase(Firestore) in React Native.
In the video I watched, he wrote and ran the code that lists the coins belonging to the user using firestore. It did not work even though I wrote the same codes. What is the reason? the code does not give an error, the screen appears, but it does not list.
The problem is that when I did the listing in console it worked. However, when I try to list with FlatList, the screen appears blank.
import { View, Text, SafeAreaView, TouchableOpacity, FlatList } from 'react-native'
import React, { useContext, useState, useEffect } from 'react'
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
import IconFA from 'react-native-vector-icons/FontAwesome'
import { deviceWidth, deviceHeight } from '../../utils/dimensions'
import { AuthContext } from '../../navigation/AuthProvider'
import { Formik, validateYupSchema } from 'formik'
import * as yup from 'yup'
import { firebase } from '#react-native-firebase/auth'
import auth from '#react-native-firebase/auth'
import firestore from '#react-native-firebase/firestore'
const color = '#aaa';
const HomeScreen = ({ navigation }) => {
const renderItem = (item) => {
<TouchableOpacity
key={item.id}
style={{
flexDirection: 'row',
width: '95%',
height: 60,
borderWidth: 1,
margin: 10,
borderRadius: 20,
padding: 10,
justifyContent: 'space-between',
alignItems: 'center'
}}>
<View style={{ flex: 1 }}>
<Text style={{
textAlign: 'left',
fontSize: 18
}}>{item.coinID}</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={{
textAlign: 'right',
fontSize: 18
}}>{item.value}</Text>
</View>
</TouchableOpacity>
}
const { signOut, user } = useContext(AuthContext);
const [currentUser, setCurrentUser] = useState({});
const [userCoinList, setUserCoinList] = useState([]);
const usersColl = firestore().collection('users');
const coinsColl = firestore().collection('coins');
const userCoinsColl = firestore().collection('userCoins');
useEffect(() => {
return usersColl
.doc(user.uid)
.get()
.then(result => {
setCurrentUser(result.data());
userCoinsColl.onSnapshot((querySnapshot) => {
let list = [];
querySnapshot.forEach(doc => {
const { userID, coinID, value } = doc.data();
if (userID == user.uid) {
list.push({
id: doc.id,
userID,
coinID,
value
});
setUserCoinList(list);
}
});
});
});
}, []);
return (
<SafeAreaView style={{ width: '100%', height: '100%' }}>
<View style={{
width: '100%',
height: '90%',
padding: 5,
flex: 1,
alignItems: 'center'
}}>
<View>
<FlatList
style={{ flex: 3, backgroundColor: '#f00' }}
data={userCoinList}
keyExtractor={item => item.id}
renderItem={renderItem}
/>
{console.log(userCoinList)}
</View>
</View>
</SafeAreaView>
)
}
export default HomeScreen
Your renderItem function does not return anything.
Here is a fix.
const renderItem = (item) => {
return <TouchableOpacity
key={item.id}
style={{
flexDirection: 'row',
width: '95%',
height: 60,
borderWidth: 1,
margin: 10,
borderRadius: 20,
padding: 10,
justifyContent: 'space-between',
alignItems: 'center'
}}>
<View style={{ flex: 1 }}>
<Text style={{
textAlign: 'left',
fontSize: 18
}}>{item.coinID}</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={{
textAlign: 'right',
fontSize: 18
}}>{item.value}</Text>
</View>
</TouchableOpacity>
}

tabBarOptions not applied to project (React Native)

I am creating a small app that has a to-do list and a calendar. At the bottom is a bottom tab navigator. Everything works and is functional, however, when I try to add style: {} inside tabBarOptions it isn't being applied. Changing activeTintColor, inactiveTintColor and labelStyle works just fine.
I tried to create a stylesheet and replace everything inside tabBarOptions, but that didn't work. My main goal is to simply create a slightly larger bar along the bottom of the screen. I don't even want a crazy custom navigation bar, just slightly larger so I can make the items inside a little bigger.
MainContainer Class:
import React from 'react';
import {StyleSheet} from 'react-native';
import {NavigationContainer} from '#react-navigation/native';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import Ionicons from 'react-native-vector-icons/Ionicons'
//screens
import Calendar from './screens/Calendar';
import ToDoList from './screens/ToDoList';
// Screen names
const calendarName = 'Calendar';
const ToDoListName = 'To Do List';
const Tab = createBottomTabNavigator();
export default function MainContainer() {
return (
<NavigationContainer>
<Tab.Navigator
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'black',
labelStyle: {paddingBottom: 10, fontSize: 10},
style: {padding: 10, height: 70},
}}
initialRouteName={ToDoListName}
screenOptions={({route}) => ({
tabBarIcon: ({focused, color, size}) => {
let iconName;
let rn = route.name;
if (rn === ToDoListName) {
iconName = focused ? 'list' : 'list-outline'; //icons in package. Change later.
} else if (rn === calendarName) {
iconName = focused ? 'calendar' : 'calendar-outline'
}
return <Ionicons name={iconName} size={size} color={color}/>
},
})}>
<Tab.Screen name={ToDoListName} component={ToDoList}/>
<Tab.Screen name={calendarName} component={Calendar}/>
</Tab.Navigator>
</NavigationContainer>
)
}
For reference here is my ToDoList class
import { KeyboardAvoidingView, StyleSheet, Text, View, TextInput, TouchableOpacity, Platform, Keyboard } from 'react-native';
import Task from '../../components/Task';
import React, { useState } from 'react';
import { ScrollView } from 'react-native-web';
export default function ToDoList() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task])
setTask(null);
}
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy)
}
return (
<View style={styles.container}>
{/* Scroll View when list gets longer than page */}
<ScrollView contentContainerStyle={{
flexGrow: 1
}} keyboardShouldPersistTaps='handled'>
{/*Today's Tasks */}
<View style={styles.tasksWrapper}>
<Text style={styles.sectionTitle}>Today's Tasks</Text>
<View style={styles.items}>
{/* This is where the tasks will go*/}
{
taskItems.map((item, index) => {
return (
<TouchableOpacity key={index} onPress={() => completeTask(index)}>
<Task text={item} />
</TouchableOpacity>
)
})
}
</View>
</View>
</ScrollView>
{/* Write a task section */}
{/* Uses a keyboard avoiding view which ensures the keyboard does not cover the items on screen */}
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.writeTaskWrapper}>
<TextInput style={styles.input} placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)} />
<TouchableOpacity onPress={() => handleAddTask()}>
<View style={styles.addWrapper}>
<Text style={styles.addText}>+</Text>
</View>
</TouchableOpacity>
</KeyboardAvoidingView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#E8EAED',
},
tasksWrapper: {
paddingTop: 20,
paddingHorizontal: 20,
},
sectionTitle: {
fontSize: 24,
fontWeight: 'bold',
},
items: {
marginTop: 30,
},
writeTaskWrapper: {
position: 'absolute',
bottom: 20,
paddingLeft: 10,
paddingRight: 10,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
input: {
paddingVertical: 15,
width: 250,
paddingHorizontal: 15,
backgroundColor: '#FFF',
borderRadius: 60,
borderColor: '#C0C0C0',
borderWidth: 1,
},
addWrapper: {
width: 60,
height: 60,
backgroundColor: '#FFF',
borderRadius: 60,
justifyContent: 'center',
alignItems: 'center',
borderColor: '#C0C0C0',
borderWidth: 1,
},
addText: {
},
});
And my Calendar class
import * as React from 'react';
import { View, Text } from 'react-native';
export default function Calendar(){
return(
<View>
<Text>Calendar Will go here</Text>
</View>
)
}
I made a Task component for the ToDoList. Not sure if you need it to solve this but I'll paste it here anyway.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { TouchableOpacity } from 'react-native-web';
const Task = (props) => {
return (
<View style={styles.item}>
<View style={styles.itemLeft}>
<View style={styles.square}></View>
<Text style={styles.itemText}>{props.text}</Text>
</View>
<View style={styles.circular}></View>
</View>
)
}
const styles = StyleSheet.create({
item: {
backgroundColor: '#FFF',
padding: 15,
borderRadius: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 20,
},
itemLeft: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
},
square: {
width: 24,
height: 24,
backgroundColor: '#55BCF6',
opacity: .4,
borderRadius: 5,
marginRight: 15,
},
itemText: {
maxWidth: '80%',
},
circular: {
width: 12,
height: 12,
borderColor: '#55BCF6',
borderWidth: 2,
borderRadius: 5
},
});
export default Task;
It sounds like you are looking for the tabBarStyle property. You should be able to rename style (which is not a supported prop of the tab navigator) to tabBarStyle.
Here's the spot in the docs that mentions this. https://reactnavigation.org/docs/bottom-tab-navigator#tabbarstyle
What I ended up doing to solve this was to put the styling inside the screenOptions. I didn't want to do this because I wanted to separate the styling from the logic, but it solved the problem for me. See code below:
export default function MainContainer() {
return (
<NavigationContainer>
<Tab.Navigator
initialRouteName={ToDoListName}
screenOptions={({route}) => ({
tabBarIcon: ({focused, color, size}) => {
let iconName;
let rn = route.name;
if (rn === ToDoListName) {
iconName = focused ? 'list' : 'list-outline'; //icons in package. Change later.
} else if (rn === calendarName) {
iconName = focused ? 'calendar' : 'calendar-outline'
}
return <Ionicons name={iconName} size={size} color={color}/>
},
activeTintColor: 'tomato',
inactiveTintColor: 'black',
tabBarShowLabel: false,
tabBarStyle: {padding: 10, height: 100, backgroundColor: 'black'},
})}>
<Tab.Screen name={ToDoListName} component={ToDoList}/>
<Tab.Screen name={calendarName} component={Calendar}/>
</Tab.Navigator>
</NavigationContainer>
)
}

Pass JSON data into another screen

1
I am a beginner and still learning to react native.
In my react native App, I have 2 screens. In the first page, I have JSON data ; I want to pass this JSON data to the next page.
I used react-navigation for navigating between pages. I need to passed each parameter for a new book screen for each book.
But I couldn't figure out, how to pass JSON data to next page! In BookScreen.js the function "getParam" is not been seen.
First Screen: ExploreScreen.js
import React, { useState, useEffect } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
Image,
TouchableOpacity,
} from "react-native";
export default function ExploreScreen({ navigation, route }) {
const [data, setData] = useState([]);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
await fetch(
"http://www.json-generator.com/api/json/get/bTvNJudCPS?indent=2"
)
.then((response) => response.json())
.then((receivedData) => setData(receivedData));
};
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.book}
onPress={() => navigation.navigate("Book", item)}
>
<Image
style={styles.bookImage}
source={{ uri: item.book_image }}
></Image>
<View>
<Text style={styles.bookTitleText}>{item.title}</Text>
<Text style={styles.bookAuthor}>{item.author}</Text>
<Text style={styles.bookGenre}>
<Text styles={styles.gen}>Genul: </Text>
{item.genre_id}
</Text>
</View>
</TouchableOpacity>
)}
></FlatList>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
alignSelf: "center",
paddingVertical: "15%",
},
book: {
flex: 1,
flexDirection: "row",
marginBottom: 3,
},
bookImage: {
width: 100,
height: 100,
margin: 5,
},
bookTitleText: {
color: "#8B0000",
fontSize: 15,
fontStyle: "italic",
fontWeight: "bold",
},
bookAuthor: {
color: "#F41313",
},
});
Second Screen: BookScreen.js
import React from "react";
import { View, Text, StyleSheet } from "react-native";
export default function BookScreen({ navigation, route }) {
const { item } = route.params;
return (
<View style={styles.container}>
<Text style={styles.text}>{navigation.getParam("name")}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
alignSelf: "center",
paddingVertical: "100%",
},
text: {
fontSize: 20,
},
});
In your BookScreen, change it to the following:
export default function BookScreen({ navigation, route }) {
const { item } = route.params;
return (
<View style={styles.container}>
<Text style={styles.text}>{item.name}</Text>
</View>
);
}
Edit:
I think you should pass the data like this:
navigation.navigate('Book', {item: item});

How can i get back my data list after searching by words?

i use Flatlist and search by words on renderHeader() function. I can filter but when i delete letters, i couldn't get main list again. I think there is a logic problem but i couln't find after trying something...
i've got a new one when i fixed problem. I tried to fix but i couldn't do that, i should put the problem in front of experts :)
import React, {Component, useState} from 'react'
import {
Text,
StyleSheet,
View,
FlatList,
SafeAreaView,
ScrollView,
Image,
TextInput,
} from 'react-native'
import data from '../../data'
const Flatlistexample = () => {
//main list state
let [list, setList] = useState(data);
//search state
const [search, setSearch] = useState('');
//search filter
searchFilter = text => {
// onChangeText
const newData = list.filter(item => {
const listItem = `${item.name.toLowerCase()} ${item.company.toLowerCase()}`
return listItem.indexOf(text.toLowerCase()) > -1;
})
setList(newData)
}
//search function
renderHeader = () =>{
return (
<View style={styles.seachContainer}>
<TextInput
style={styles.textInput}
placeholder={'Search...'}
value={search}
onChangeText={text => {
//setStates
searchFilter(text)
setSearch(text)
}}></TextInput>
<Text
style={{
alignItems: 'flex-start',
color: 'black',
fontSize: 22,
}}>
{search}
</Text>
</View>
)
}
return (
<SafeAreaView
style={{
flex: 1,
}}>
<FlatList
data={list}
renderItem={({item, index}) => {
return (
<ScrollView>
<SafeAreaView
style={[
styles.container,
{backgroundColor: index % 2 === 0 ? '#fafafa' : '#bbb'},
]}>
<Image style={styles.profile} source={{uri: item.picture}} />
<View style={styles.rightside}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.company}>{item.company}</Text>
</View>
</SafeAreaView>
</ScrollView>
)
}}
keyExtractor={item => item._id}
//called search function
ListHeaderComponent={renderHeader()}
/>
</SafeAreaView>
)
}
export default Flatlistexample
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
borderColor: 'gray',
},
profile: {
width: 50,
height: 50,
borderRadius: 25,
marginLeft: 10,
},
rightside: {
marginLeft: 20,
justifyContent: 'space-between',
marginVertical: 5,
},
name: {
fontSize: 22,
marginBottom: 10,
},
searchContainer: {
padding: 10,
borderWidth: 2,
borderColor: 'gray',
},
textInput: {
fontSize: 16,
backgroundColor: '#f9f9f9',
padding: 10,
},
})
Thanks for your help
Filter Data
onSearchText = (value) => {
this.setState({searchText: value})
if(value.trim() == "" || value == null){
this.setState({list: this.state.list}
} else {
let filter = this.state.list.fillter(data => {
// data fillter logic //
return data;
})
this.setState({filterData: filter})
}
Render FlatList
<FlatList
extradata={this.state}
data={searchText ? filterData : list}
/>
I fixed...
How?
My main data state is constant, i'm filtering on data list with filter state. So my data list doesn't change anytime.
import React, {Component, useState} from 'react'
import {
Text,
StyleSheet,
View,
FlatList,
SafeAreaView,
ScrollView,
Image,
TextInput,
} from 'react-native'
import data from '../../data'
const Flatlistexample = () => {
//main list state
let [list, setList] = useState(data)
//search state
const [search, setSearch] = useState('')
//filter state
const [updated, setUpdated] = useState(data)
//search filter
searchFilter = text => {
// onChangeText
if (text) {
const newData = list.filter(item => {
const listItem = `${item.name.toLowerCase()} ${item.company.toLowerCase()}`
return listItem.indexOf(text.toLowerCase()) > -1
})
setUpdated(newData)
}
//search function
renderHeader = () => {
return (
<View style={styles.seachContainer}>
<TextInput
style={styles.textInput}
placeholder={'Search...'}
value={search}
onChangeText={text => {
searchFilter(text)
setSearch(text)
}}></TextInput>
<Text
style={{
alignItems: 'flex-start',
color: 'black',
fontSize: 22,
}}>
{search}
</Text>
</View>
)
}
return (
<SafeAreaView
style={{
flex: 1,
}}>
<FlatList
//i'm showing filter state
data={updated}
renderItem={({item, index}) => {
return (
<ScrollView>
<SafeAreaView
style={[
styles.container,
{backgroundColor: index % 2 === 0 ? '#fafafa' : '#bbb'},
]}>
<Image style={styles.profile} source={{uri: item.picture}} />
<View style={styles.rightside}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.company}>{item.company}</Text>
</View>
</SafeAreaView>
</ScrollView>
)
}}
keyExtractor={item => item._id}
//called search function
ListHeaderComponent={renderHeader()}
/>
</SafeAreaView>
)
}
export default Flatlistexample
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
borderColor: 'gray',
},
profile: {
width: 50,
height: 50,
borderRadius: 25,
marginLeft: 10,
},
rightside: {
marginLeft: 20,
justifyContent: 'space-between',
marginVertical: 5,
},
name: {
fontSize: 22,
marginBottom: 10,
},
searchContainer: {
padding: 10,
borderWidth: 2,
borderColor: 'gray',
},
textInput: {
fontSize: 16,
backgroundColor: '#f9f9f9',
padding: 10,
},
})
/*
else if(text.length > uzunluk){
setList(data)
const newData = list.filter(item => {
const listItem = `${item.name.toLowerCase()} ${item.company.toLowerCase()}`
return listItem.indexOf(text.toLowerCase()) > -1;
})
setList(newData)
}else if(text.length<uzunluk){
setList(data)
const newData = list.filter(item => {
const listItem = `${item.name.toLowerCase()} ${item.company.toLowerCase()}`
return listItem.indexOf(text.toLowerCase()) > -1;
})
setList(newData)
}
*/

How to force Flatlist to re-render after getting a single data?

I've faced an issue with flatlist when I get single data from the server and set these into state and passes into data props, I can't see any update in the render "I'm adding some Loading if I'd not received any data I show an let's say Indicator" so the indicator disappears and I see blank screen!!
FYI, When I enable Hot Reloading and just press Save in my IDE I can see the single Data in my Screen!
So how can I force it to appear the data!
Code
import React, { Component } from "react";
import firebase from "react-native-firebase";
import Icon from "react-native-vector-icons/Ionicons";
import _ from "lodash";
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
Image,
Dimensions
} from "react-native";
const { width } = Dimensions.get("screen");
class ListChats extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
noChat: true
};
}
_getUsers = () => {
let currentUser = firebase.auth().currentUser.uid;
let ref = firebase.database().ref(`Messages/${currentUser}`);
let usersKeys = [];
ref.once("value").then(snapshot => {
snapshot.forEach(childsnap => {
usersKeys.push(childsnap.key);
});
let usernames = [];
usersKeys.forEach(key => {
firebase
.database()
.ref("users")
.child(key)
.once("value")
.then(usersShot => {
let username = usersShot.val().username;
usernames.push({ username: username, key: key });
});
});
this.setState({ users: usernames,noChat: false });
});
};
componentDidMount() {
this._getUsers();
}
render() {
if (this.state.noChat) {
console.log("IF", this.state.users);
return (
<View style={styles.container}>
<Image
style={{
width,
height: width * 0.7,
resizeMode: "contain"
}}
source={require("../../assets/empty.gif")}
/>
<Text style={{ alignSelf: "center" }}>No Chats Found</Text>
</View>
);
} else {
console.log("Else", this.state.users);
return (
<View style={styles.container}>
<FlatList
key={Math.random() * 1000}
extraData={this.state} // I'm already added
data={this.state.users}
contentContainerStyle={{ flexGrow: 1 }}
renderItem={({ item }) => {
return (
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate("ChatDetails", {
Key: item.key,
userName: item.username
})
}
>
<View style={styles.parent}>
<Icon name="ios-contact" size={50} color="#4d8dd6" />
<View
style={{
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
marginHorizontal: 25
}}
>
<Text
style={{
color: "#000",
fontSize: 17
// marginHorizontal: 25
// alignSelf: "stretch"
}}
>
{item.username}
</Text>
{/* <Text
style={{
color: "#a1a1a1",
marginHorizontal: 35,
marginVertical: 5,
alignSelf: "stretch"
}}
numberOfLines={1}
lineBreakMode="tail"
>
{item.lastMssg.text}
</Text> */}
</View>
<Icon name="ios-chatboxes" size={25} color="#d6d6d6" />
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center"
},
parent: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 25,
marginHorizontal: 15,
borderBottomWidth: 1,
borderBottomColor: "#eee"
}
});
export default ListChats;
You have used extra data props but the issue is flatlist does a shallow data comparison so when the length of users changes it won't affect the flatlist so replace it with this.state.users
extraData={this.state.users}
You can see in the documentation it says flatlist is implementation of PureCompoment and PureComponent does a shallow comparison thats the reason it is not re-rendering.
https://reactnative.dev/docs/flatlist#extradata
to force the flatlist re-render. just add a boolean lets say "forceUpdate" to your state and whenever an item is added to flatlist data toggle that boolean and pass that to the extra data of your FlatList instead of the state.

Categories

Resources