FlatList is not rerendering upon data change - reactjs

I have an array of conversations and a function that checks if the recipient is online. However when that function changes its return value (boolean), the flatlist does NOT rerender. I tried extraData but with no luck. My code is:
flatList
<FlatList
data={conversations}
extraData={isRefreshing}
renderItem={(conversation) => (
<TouchableOpacity
onPress={() => selectConversation(conversation.item)}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
borderBottomWidth: 1,
padding: 5,
marginBottom: 5,
}}
>
<View>
<Text>{getRecipient(conversation.item).displayName}</Text>
<Text>{conversation.item.latestMessage}</Text>
</View>
{isUserOnline(conversation.item) && (
<Text style={{ color: "green" }}>ONLINE</Text>
)}
</TouchableOpacity>
)}
keyExtractor={(item) => item._id}
/>
useEffect
useEffect(() => {
let handler = (data) => {
console.log(data);
setOnlineUsers((prevState) => {
return data;
});
setRefreshing(!isRefreshing);
};
const unsubscribe = navigation.addListener("focus", () => {
console.log("focused");
socket.on("online-users-data", handler);
socket.emit("join-online-users");
getConversations();
});
return () => {
socket.emit("leave-online-users");
socket.off("online-users-data", handler);
unsubscribe();
};
}, []);
isUserOnline()
function isUserOnline(conversation) {
const user = getRecipient(conversation);
const onlineUser = onlineUsers.find((u) => u.userId === user._id);
return (
user && onlineUser && user._id.toString() === onlineUser.userId.toString()
);
}

I have solved this by adding onlineUsers to extraData. Damn it was confusing.

Related

ReactNative TCP Socket

I'm trying to updates states using RN TCP Socket library : https://github.com/Rapsssito/react-native-tcp-socket
But when i received data, state is not updated (Log is correctly displayed).
const App = () => {
const [connected, setConnected] = useState('Init');
const [numberMessage, setNumberMessage] = useState(0);
const sendMessage = val => {
// client.emit('data', {message: val});
console.log(numberMessage);
};
useEffect(() => {
client.on('connect', () => {
setConnected('Connected');
});
client.on('data', async data => {
const increment = numberMessage + 1;
await setNumberMessage(increment);
console.log('received');
});
return () => {
client.destroy();
};
}, []);
return (
<SafeAreaView style={{backgroundColor: 'white', flex: 1}}>
<ScrollView style={{flex: 1}}>
<Text style={{textAlign: 'center', marginBottom: 25}}>{connected}</Text>
<Text style={{marginBottom: 10}}>{numberMessage}</Text>
<TouchableOpacity onPress={() => sendMessage('yes')}>
<Text>Send message</Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
I can't find a solution. Have you some tips or alternatives ?

Load component after api is fetched

I am fetching json from API and then I want to display the component
const [jsonCat, setjsonCat] = useState([]);
useEffect(() => {
refreshCat();
}, []);
const refreshCat = async () => {
try {
console.log("refreshing categories");
setjsonCat([]);
getCat().then((response) => {
setjsonCat(response);
});
} catch (e) {
console.log(e);
}
};
const CarousalLoop = (props) => {
const BANNER_Hs = 1;
if (jsonCat.length == 0) {
return <View></View>;
}
const listItemstest = jsonCat.map((link) => {
console.log(link.name);
<View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>;
});
return (
<View style={{ height: 220, backgroundColor: "brown" }}>
{listItemstest}
</View>
);
};
And Finally my render component has
{jsonCat.length ? <CarousalLoop /> : <ActivityIndicator />}
When I run this code , Activity indicator is shown until API request is fetched and then json is also received properly , console.log(link.name)
is printing the names correctly but CarousalLoop is displayed without any listitems (just brown view component is shown) .
Either you use return keyword
const listItemstest = jsonCat.map((link) => {
console.log(link.name);
return(
<View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>
)
});
Or wrap in parenthesis
const listItemstest = jsonCat.map((link) => (
<View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>;
));
You need to return the list of items to render from listItemstest, like this.
const listItemstest = jsonCat.map((link) => {
console.log(link.name);
return <View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>;
});

Check visibility of item at the screen

I have FlastList with data. I need to check if item from the Flastlist is seen. So if i do not see data at my screen i do not have to do anything, but if i see i have to console.log data info. And when i am scrolling i have to console.log data that is visibile. I am trying to use onViewableItemsChanged with viewabilityConfig, but when i console.log data, it returns all data from FlastList but not data that is seen. Help me please.
I will be very thankfull!
_onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems.map(item => item.item.text));
};
_viewabilityConfig = {
viewAreaCoveragePercentThreshold: 100
};
//....
<FlatList
data={this.state.postData}
initialNumToRender={0}
ItemSeparatorComponent = {this.FlatListItemSeparator}
renderItem={({item}) => {
return (
<View style={{paddingTop: 15}}
ref={ (divElement) => { this.divElement = divElement } }
>
// data
)}
}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={this._viewabilityConfig}
keyExtractor={item => item.id}
/>
In your comment you specified that the FlatList is inside a view and scrollview. I tried to reproduce it this way:
<View style={styles.container}>
<ScrollView style={styles.scrollView}>
<FlatList
data={DATA}
initialNumToRender={0}
renderItem={renderItem}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
keyExtractor={item => item.id}
/>
</ScrollView>
</View>
And indeed, console.log shows all data in this case. When I removed ScrollView so that the FlatList is inside the View element (with flex:1) then it works correctly - console.log shows only visible elements. My code:
const DATA = [
{
id: '1',
title: 'First Item',
},
// ... more elements
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const renderItem = ({item}) => {
return (
<View style={{paddingTop: 15}}
ref={ (divElement) => { this.divElement = divElement } }
>
<Item title={item.title} />
</View>
)
}
const viewabilityConfig = {
viewAreaCoveragePercentThreshold: 100
};
const onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems.map(item => item.item.title));
};
export default function App() {
return (
<View style={styles.container}>
<FlatList
data={DATA}
initialNumToRender={0}
renderItem={renderItem}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
keyExtractor={item => item.id}
/>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});

React Native Hooks Search Filter FlatList

I am trying to make a Covid19 React Native Expo app. It contains a search filter from which user will select a country then the selected country results will be shown to the user. I keep getting this error on my Android device "Unexpected Identifier You" while on web pack the countries load but they don't filter correctly.
Working Snack Link: https://snack.expo.io/#moeez71/ac5758
Here is my code:
import React, { useState, useEffect } from "react";
import {
ActivityIndicator,
Alert,
FlatList,
Text,
StyleSheet,
View,
TextInput,
} from "react-native";
export default function ABCDEE() {
const [arrayholder, setArrayholder] = useState([]);
const [text, setText] = useState("");
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const fetchAPI = () => {
return fetch("https://api.covid19api.com/countries")
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson);
setLoading(false);
setArrayholder(responseJson);
})
.catch((error) => {
console.error(error);
});
};
useEffect(() => {
fetchAPI();
});
const searchData = (text) => {
const newData = arrayholder.filter((item) => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setData(newData);
setText(text);
};
const itemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
};
return (
<View>
{loading === false ? (
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={(text) => searchData(text)}
value={text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={itemSeparator}
renderItem={({ item }) => (
<Text style={styles.row}>{item.Country}</Text>
)}
style={{ marginTop: 10 }}
/>
</View>
) : (
<Text>loading</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: "center",
flex: 1,
margin: 5,
},
row: {
fontSize: 18,
padding: 12,
},
textInput: {
textAlign: "center",
height: 42,
borderWidth: 1,
borderColor: "#009688",
borderRadius: 8,
backgroundColor: "#FFFF",
},
});
You had made two mistakes in the above code
useEffect second parameter should be a empty array for it act as componentDidMount()
useEffect(() => {
fetchAPI();
},[])
in FlatList renderItem need to destructure the item.
renderItem={( {item} ) => <Text style={styles.row}
>{item.Country}</Text>}
Working code
import React, {useState, useEffect} from "react"
import { ActivityIndicator, Alert, FlatList, Text, StyleSheet, View, TextInput } from 'react-native';
export default function ABCDEE(){
const [arrayholder,setArrayholder] =useState([])
const[text, setText] = useState('')
const[data, setData] = useState([])
const [loading , setLoading] = useState(true)
const fetchAPI = ()=> {
return fetch('https://api.covid19api.com/countries')
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson)
setLoading(false)
setArrayholder(responseJson)
}
)
.catch((error) => {
console.error(error);
});
}
useEffect(() => {
fetchAPI();
},[])
const searchData= (text)=> {
const newData = arrayholder.filter(item => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1
});
setData(newData)
setText(text)
}
const itemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
return (
<View style={{flex:1}} >
{loading === false ?
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={(text) => searchData(text)}
value={text}
underlineColorAndroid='transparent'
placeholder="Search Here" />
<FlatList
data={data}
keyExtractor={ (item, index) => index.toString() }
ItemSeparatorComponent={itemSeparator}
renderItem={( {item} ) => <Text style={styles.row}
>{item.Country}</Text>}
style={{ marginTop: 10 }} />
</View>
: <Text>loading</Text>}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
flex: 1,
margin: 5,
},
row: {
fontSize: 18,
padding: 12
},
textInput: {
textAlign: 'center',
height: 42,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 8,
backgroundColor: "#FFFF"
}
});
There are multiple issues with your implementation. I will point out some mistake/ignorance. You can clean up you code accordingly.
Do not create 2 state to keep same data. ie. arrayholder and data.
Change text value on search, don't the data. based on that text filter
Hooks always define some variable to be watched.
Update: Seems there is an issue with flex in android view, i use fixed height it is visible.
Just a hack for android issue. minHeight
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
flex: 1,
margin: 5,
minHeight: 800,
},
Working link: https://snack.expo.io/kTuT3uql_
Updated code:
import React, { useState, useEffect } from 'react';
import {
ActivityIndicator,
Alert,
FlatList,
Text,
StyleSheet,
View,
TextInput,
} from 'react-native';
export default function ABCDEE() {
const [text, setText] = useState('');
const [state, setState] = useState({ data: [], loading: false }); // only one data source
const { data, loading } = state;
const fetchAPI = () => {
//setState({data:[], loading: true});
return fetch('https://api.covid19api.com/countries')
.then(response => response.json())
.then(data => {
console.log(data);
setState({ data, loading: false }); // set only data
})
.catch(error => {
console.error(error);
});
};
useEffect(() => {
fetchAPI();
}, []); // use `[]` to avoid multiple side effect
const filterdData = text // based on text, filter data and use filtered data
? data.filter(item => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
})
: data; // on on text, u can return all data
console.log(data);
const itemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: '100%',
backgroundColor: '#000',
}}
/>
);
};
return (
<View>
{loading === false ? (
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={text => setText(text)}
value={text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={filterdData}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={itemSeparator}
renderItem={({ item }) => (
<Text style={styles.row}>{item.Country}</Text>
)}
style={{ marginTop: 10 }}
/>
</View>
) : (
<Text>loading</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
//flex: 1,
margin: 5,
height: 800,
},
row: {
fontSize: 18,
padding: 12,
},
textInput: {
textAlign: 'center',
height: 42,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 8,
backgroundColor: '#333',
},
});
It seems like the error comes from the catch block when you fetch your data. The response comes with a 200 status, so it's not an issue with the endpoint itself. I console.logged the response and it seems fine. The problem is when you parse the response and try to use it in the second then() block, so the catch block fires up. I could not debug it right in the editor you use, but I would check what type of object I'm receiving from the API call.
This is not a direct answer to your question but I didn't want this to get lost in the comments as it is directly related with your efforts on this app.
App Store and Google Play Store no longer accept apps that have references to Covid-19 https://developer.apple.com/news/?id=03142020a
You can only publish apps about Covid-19 if you are reputable source like a government's health department.
Therefore, I urge you to reevaluate your efforts on this app.

Flatlist with calling API on scroll does not work properly

I am using Function component in react native.
In that I am use Flatlist for data view. on onEndReached function of FlatList i am calling API for fetching data.
My Problem is
on scroll API call twice some times
on the Finish of all data. ActivityIndicator is showing in ListFooterComponent function.
Can you please help me.
Thanks in Advance.
Here is my code:
import React, { useState, useEffect } from "react";
import {
View,
StyleSheet,
ActivityIndicator,
Image,
FlatList,
TouchableHighlight,
} from "react-native";
import MainStyle from "../constants/Style";
import Layout from "../constants/Layout";
import Colors from "../constants/Colors";
import Services from "../Services";
const LIMIT_PER_PAGE = 10;
let fetching = false;
let isSubscribed = true;
let isListEnd = false;
let pageNo = 1;
var sortby = "latest";
let width = Layout.window.width / 2 - 16;
export default function PostListScreen(props) {
const [posts, setPosts] = useState([]);
fetchAllLatestPost = function () {
console.log("fetchAllLatestPost", fetching, isListEnd);
if (!fetching && !isListEnd) {
fetching = true;
Services.getAllPost(sortby, pageNo, LIMIT_PER_PAGE)
.then(function (res) {
if (!!res) {
console.log("res.length", res.length)
if (!!res.length && isSubscribed) {
pageNo++;
setPosts((posts) => {
posts = [...posts, ...res];
return posts;
});
} else {
isListEnd = true;
}
fetching = false;
}
});
}
};
useEffect(() => {
fetching = false;
isListEnd = false;
pageNo = 1;
isSubscribed = true;
fetchAllLatestPost();
return () => (isSubscribed = false);
}, []);
let readyImage = function (post) {
if (!!post.urls && !!post.urls.regular) {
return typeof post.urls.regular === "string"
? { uri: post.urls.regular }
: post.urls.regular;
} else {
return require("../assets/images/logo_white.png");
}
};
let renderItem = function (post, index) {
return (
<TouchableHighlight onPress={() => onPressPost(post)} key={index}>
<View style={styles.item}>
<Image
source={readyImage(post)}
style={{
flex: 1,
width: null,
height: null,
resizeMode: "cover",
borderRadius: 4,
}}
/>
</View>
</TouchableHighlight>
);
};
let renderFooter = function () {
return (
<View style={styles.footer}>
{console.log("fetching in footer ", fetching)}
{!!fetching ? (
<ActivityIndicator color="white" style={{ margin: 15 }} />
) : null}
</View>
);
};
return (
<View style={MainStyle.wrapper}>
<FlatList
numColumns={2}
keyExtractor={(item, index) => index.toString()}
data={posts}
onEndReached={() => fetchAllLatestPost()}
onEndReachedThreshold={0.5}
renderItem={({ item, index }) => <View>{renderItem(item, index)}</View>}
getItemLayout={(data, index) => ({
length: width,
offset: (width/2) * index,
index,
})}
ListFooterComponent={renderFooter()}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
},
item: {
height: width,
width: width,
margin: 8,
},
footer: {
padding: 10,
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
},
});
This is how I handled it
<FlatList
data={this.state.posts}
onRefresh={() => {
if (!this.props.posts.fetching) {
this.setState({
page: 1,
checkData: true,
posts: [],
});
this.props.postsRequest({page: 1});
}
}}
// onEndReached={}
keyExtractor={item => item.uid}
onMomentumScrollEnd={() => {
if (!this.props.posts.fetching) {
this.props.postsRequest({page: this.state.page + 1});
this.setState({page: this.state.page + 1, checkData: true});
}
}}
onEndReachedThreshold={0}
refreshing={this.props.posts.fetching}
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingBottom: 50}}
style={{padding: 10, marginVertical: 10}}
renderItem={item => (
<View style={{marginVertical: 5}}>
<Posts post={item.item} navigation={this.props.navigation} />
</View>
)}
/>

Resources