Encountered two children with the same key - React Native Gifted Chat - react-native

The bellow code runs somewhat to what I want. I'm able to get the correct data when i enter the component, but as soon as I send a message, it duplicates the previous messages. I'm quite new to React Native so just learning. Any help or suggestions would be appreciated!
1.) Fetch documents where 'selectedUser (local) == field 'to' in Firebase.
2.) Display chats from the found documents.
3.) Send message to screen and firebase.
4.) Profit.
useEffect(() => {
const q = query(collectionRef, orderBy('createdAt', 'desc'));
const unsubscribe = onSnapshot(q, querySnapshot => {
const id = querySnapshot.docs.map(doc => ({
_id: doc.data()._id,
createdAt: doc.data().createdAt.toDate(),
text: doc.data().text,
user: doc.data().user,
to: doc.data().to
}))
id.forEach((val)=>{
if(val.to == selectedUser[0]){
console.log(val.to)
DATA.push({
_id: val._id,
createdAt: val.createdAt,
text: val.text,
user: val.user,
to: val.to,
}
);
}
})
// console.log(DATA)
setMessages(DATA);
// console.log(Message)
});
return () => unsubscribe();
}, []);
const onSend = useCallback((messages = []) => {
const to = selectedUser.toString();
const { _id, createdAt, text, user } = messages[0]
addDoc(collection(db, 'Chats'), { _id, createdAt, text, user, to });
}, []);
return (
<SafeAreaView style={styles.container}>
<Text style={styles.ChattingWith}>Chatting with: {selectedUser[[0]]}</Text>
<View style={styles.MainFeed}>
<GiftedChat
messages={messages}
showAvatarForEveryMessage={true}
onSend={messages => onSend(messages)}
user={{
_id: auth?.currentUser?.email,
avatar: auth?.currentUser?.photoURL
}}
/>
</View>
</SafeAreaView>
)
}
What Firestore has when 'onSend' function is called:

can you try replacing
id.forEach((val)=>{
if(val.to == selectedUser[0]){
console.log(val.to)
DATA.push({
_id: val._id,
createdAt: val.createdAt,
text: val.text,
user: val.user,
to: val.to,
}
);
}
})
setMessages(DATA);
with
const newData = id.filter((val)=> val.to == selectedUser[0])
setMessages(newData);

Related

React native contacts in list works really slow

I'm trying to add a feature to my app which is add your contacts to your profile. I'm using a package for this but it works slow (in my case 470 contact record I got in my phone).
The package
import Contacts from 'react-native-contacts';
My code
componentDidMount() {
this.getContacts();
}
getContacts() {
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS, {
title: 'Contacts',
message: 'This app would like to view your contacts.',
buttonPositive: 'Please accept bare mortal',
})
.then(
Contacts.getAllWithoutPhotos().then(contacts => {
var contactss = [];
contacts.map(contact => {
/// I'm mapping it for showing them in simple list with checkbox
contactss.push({...contact, checked: false});
});
// then sorting for alphabetical order by displayName
contactss.sort(function (a, b) {
if (a.displayName.toLowerCase() < b.displayName.toLowerCase())
return -1;
if (a.displayName.toLowerCase() > b.displayName.toLowerCase())
return 1;
return 0;
});
this.setState(
{contacts: contactss, loaded: true},
() => {
console.log('contacts', this.state.contacts);
},
);
}),
)
.then(contacts => {});
}
That's all code. Is this normal or should I do what?
Thank you.
I'm just trying to handle with this data. Also I'm giving select option to user which account you want, like this.
//function to select contact
checkContc(contc) {
console.log('checkFriend', contc);
let contactsTemp = this.state.contactsTemp.map(el =>
el.recordID == contc.recordID
? {...el, checked: !el.checked}
: {...el},
);
this.setState({contactsTemp}, () => {
console.log('check frined stateD ', this.state);
});
}
// render in scrollview
<ScrollView>
{this.state.contactsTemp?.map((follower, index) => {
return (
<TouchableOpacity
onPress={() => {
this.checkFriend(follower);
}}>
<FriendsComponent
checked={follower.checked}
nameSurname={follower.displayName}
key={index}
/>
</TouchableOpacity>
);
})}
</ScrollView>
result
I made a research and it looks like it is that slow. I'm not sure if the only solution isn't the native one.

React Native Workflow, handle 429 erros and data

im looking for a bit of guideness here, im working on a RN app with redux and everytime i enter a new screen on the app, must likely i have a "callinitialData" function inside my useEffect(), using axios to fetch api data to be dispatch() to the redux state.
Everything works but whenever i jump from screen to screen to fast, sometimes i get a 429 error of to many request, so i just setup the redux-persist hoping that would help reduce the amount of request,in my mind thinking that if my api data is equal to my local data, that request wouldnt be necessary to be made.
However it stays the same so i was thinking what would be the best aproach here, on login try to fetch all the data at once > store it at asyncstorage and redux, and fetch that on each screen ?
how would i be able then, if i fetch all the data on login, receive the new data sets from the api in real time?
App functionality -
Edit Profile (img, pass, email, name)
Data Forms (requeast X, submit data, edit forms)
Chat by contacts / create Group chat
Code Example
const ChatScreen = ({ auth: { user }, getChatContacts, chat: { contacts }, navigation }) => {
useEffect(() => {
getChatContacts();
}, []);
const onChatUser = async (_id, name, roomID) => {
console.log(_id, name, roomID, contacts.payload.clone)
navigation.navigate( "Message", {
_id, name, chatRoomId: roomID, allUsers: contacts.payload.clone
});
}
const renderItem = ({ item , index } ) => {
let userName = "";
item.users.map((users, index) => {
let idToCheck = users.toString() !== user._id.toString() ? users : false;
if (idToCheck) {
let getOneUser = contacts.payload.clone.find(x => x._id === idToCheck);
userName += "" + getOneUser.name + ", ";
}
})
return (<TouchableOpacity key={item._id} onPress={() => onChatUser(item._id, item.name, item.roomID)}>
<View style={styles.chatContainer}>
<FontAwesome name="user-circle-o" size={50} color="#000000"/>
<Text style={styles.chatTitle}>{ ((userName).length > 32) ?
(((userName).substring(0,32-3)) + '...') :
userName }</Text>
<FontAwesome name="angle-right" size={25} color="#000000"/>
</View>
</TouchableOpacity>)
};
return (
<SafeAreaView style={styles.container}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
placeholder="Search friend"
style={styles.chatsearch}
/>
{contacts ?
(<FlatList
data={contacts.payload.allContact}
renderItem={(item, index) => renderItem(item, index)}
keyExtractor={item => item.id}
style={styles.FlatListContainer}
/>) : (<Text style={styles.FlatListContainer}></Text>)
}
</SafeAreaView>
);
}
const styles = StyleSheet.create({});
ChatScreen.propTypes = {
isAuthenticated: PropTypes.bool,
auth: PropTypes.object,
};
const mapStateProps = state => ({
auth: state.auth,
chat: state.chat
});
export default connect(mapStateProps, {getChatContacts} )(ChatScreen);
Redux Action
export const getChatContacts = () => async dispatch => {
const config = {
header: {
"Content-Type": "application/json"
}
}
try {
const res = await axios.get(API_LINK +"/users/getChatContacts",);
dispatch({
type: GET_CONTACT_CHAT,
payload: res.data
});
} catch (err){
console.log(err)
dispatch({
type: ERROR_FAMILY_PARENT,
payload: { msg: err.response, status: err.response}
});
}
};

React Native Gifted Chat + Firestore not showing messages correctly?

I am trying to create a chat feature in my react native app. I am using react-native-gifted-chat and saving the messages in firestore. Here is the behavior that is occurring:
When I send a message, ALL the messages re render, some of them are duplicates, as you can see I only have 3 messages sent so far, but all these duplicates are making me wonder why the entire thing is re-rendering and why there are duplicates when it does re-render.
The code:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = {
messages: [],
currentUser: null,
isLoading: true,
messageID: ""
}
}
//---------------------------------------------------------------
async componentDidMount (){
// get user info from firestore
let userUID = Firebase.auth().currentUser.uid
await Firebase.firestore().collection("users").doc(userUID).get()
.then(doc => {
data = doc.data()
this.setState({
currentUser: {
name: data.username,
avatar: data.profilePic,
_id: doc.id,
},
})
})
const messages = []
await Firebase.firestore().collection("chat")
.orderBy("createdAt", "desc")
.limit(50)
.onSnapshot(querySnapshot => {
querySnapshot.forEach((res) => {
const {
user,
text,
createdAt,
} = res.data();
messages.push({
key: res._id,
user,
text,
createdAt,
});
})
this.setState({
messages,
isLoading: false,
});
})
}
//Load 50 more messages when the user scrolls
//
//Add a message to firestore
onSend = async(message) => {
await Firebase.firestore().collection("chat")
.add({
user: {
_id: this.state.currentUser._id,
name: this.state.currentUser.name,
avatar: this.state.currentUser.avatar,
},
})
.then(ref => this.setState({messageID: ref.id}))
await Firebase.firestore().collection("chat")
.doc(this.state.messageID)
.set({
_id: this.state.messageID,
text: message[0].text,
createdAt: message[0].createdAt
}, { merge: true })
}
render() {
if(this.state.isLoading){
return(
<View style = {{backgroundColor: '#000000', flex: 1}}>
<ActivityIndicator size="large" color="#9E9E9E"/>
</View>
)
}
return (
<View style={{backgroundColor: '#000000', flex: 1}}>
<GiftedChat
showUserAvatar={true}
renderUsernameOnMessage={true}
messages={this.state.messages}
onSend={message => this.onSend(message)}
scrollToBottom
/>
</View>
)
}
}
Some notes:
Every time the component mounts, the messages array pushes the messages to the state array.
The component mounts when I send a message, thus re-rendering the array of messages
Each message ID is unique and generated by firebase using "Add"
Let me know how I can fix this issue! thanks
Duplication is because of just single line
const messages = []
Move this line inside listener, i.e.onSnapShot()
await Firebase.firestore().collection("chat")
.orderBy("createdAt", "desc")
.limit(50)
.onSnapshot(querySnapshot => {
const messages = []
// rest of your code which is having forEach loop
});
The issue was that messages object was created only once when the component loaded, and you were pushing elements to that object only.

How to display username of sender in chat app?

I am using react-native-gifted-chat for my UI and for the backend, I am using AWSAppSync/GraphQL to store user data. I am not so sure how to go about displaying username of sender in my ChatApp and would need this feature for group chats. So far, this is my code.
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
class ChatScreen extends React.Component {
state = {
messages: [],
username: ''
}
componentWillMount() {
this.setState({
messages: [
{
_id: 1,
text: 'Hello developer',
createdAt: new Date(),
user: {
_id: 0,
name: 'Default',
avatar: 'https://placeimg.com/140/140/any',
},
},
],
})
}
onSend(messages = []) {
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, messages),
}))
}
render() {
return (
<GiftedChat
messages={this.state.messages}
renderUsernameOnMessage={true}
onSend={messages => this.onSend(messages)}
user={this.username}
/>
)
}
}
export default ChatScreen;
Did you try this?
<GiftedChat
messages={this.state.messages}
renderUsernameOnMessage={true}
onSend={messages => this.onSend(messages)}
user={this.state.username}
/>
In react native gifted chat user must be object containing the keys _id, name and avatar. _id will help it to separate sender and receiver. So yes, you have to set user with these properties. It not allowe only username.
So simple way to do this is that first fetch current user info from the server and store it into redux of asyncStorage. For example I am fetching current user from firebase as below :
export const getAccountInfo = () => {
return async dispatch => {
const cu = auth().currentUser;
const user = await firestore().collection('users').doc(cu.uid).get();
dispatch({
type: Types.INFO_FETCHED,
data: user.data()
});
};
};
Now, In my chat screen I set user as below :
<GiftedChat
...
...
user={{
_id: this.props.account?.user?.uid,
name: this.props.account?.data?.fullName,
avatar: this.props.account?.data?.avatar
}}
/>

React Native Flat List doesn't call onEndReached handler after two successful calls

I implement a very simple list that calls a server that returns a page containing books.Each book has a title, author, id, numberOfPages, and price). I use a Flat List in order to have infinite scrolling and it does its job very well two times in a row (it loads the first three pages) but later it doesn't trigger the handler anymore.
Initially it worked very well by fetching all available pages, but it stopped working properly after I added that extra check in local storage. If a page is available in local storage and it has been there no longer than 5 seconds I don't fetch the data from the server, instead I use the page that is cached. Of course, if there is no available page or it is too old I fetch it from the server and after I save it in local storage.(Something went wrong after adding this behavior related to local storage.)
Here is my component:
export class BooksList extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 0
};
}
async storePage(page, currentTime) {
try {
page.currentTime = currentTime;
await AsyncStorage.setItem(`page${page.page}`, JSON.stringify(page));
} catch (error) {
console.log(error);
}
}
subscribeToStore = () => {
const { store } = this.props;
this.unsubsribe = store.subscribe(() => {
try {
const { isLoading, page, issue } = store.getState().books;
if (!issue && !isLoading && page) {
this.setState({
isLoading,
books: (this.state.books ?
this.state.books.concat(page.content) :
page.content),
issue
}, () => this.storePage(page, new Date()));
}
} catch (error) {
console.log(error);
}
});
}
componentDidMount() {
this.subscribeToStore();
// this.getBooks();
this.loadNextPage();
}
componentWillUnmount() {
this.unsubsribe();
}
loadNextPage = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 },
async () => {
let localPage = await AsyncStorage.getItem(`page${this.state.pageNumber}`);
let pageParsed = JSON.parse(localPage);
if (localPage && (new Date().getTime() - localPage.currentTime) < 5000) {
this.setState({
books: (
this.state.books ?
this.state.books.concat(pageParsed.content) :
page.content),
isLoading: false,
issue: null
});
} else {
const { token, store } = this.props;
store.dispatch(fetchBooks(token, this.state.pageNumber));
}
});
}
render() {
const { isLoading, issue, books } = this.state;
return (
<View style={{ flex: 1 }}>
<ActivityIndicator animating={isLoading} size='large' />
{issue && <Text>issue</Text>}
{books && <FlatList
data={books}
keyExtractor={book => book.id.toString()}
renderItem={this.renderItem}
renderItem={({ item }) => (
<BookView key={item.id} title={item.title} author={item.author}
pagesNumber={item.pagesNumber} />
)}
onEndReachedThreshold={0}
onEndReached={this.loadNextPage}
/>}
</View>
)
}
}
In the beginning the pageNumber available in the state of the component is 0, so the first time when I load the first page from the server it will be incremented before the rest call.
And here is the action fetchBooks(token, pageNumber):
export const fetchBooks = (token, pageNumber) => dispatch => {
dispatch({ type: LOAD_STARTED });
fetch(`${httpApiUrl}/books?pageNumber=${pageNumber}`, {
headers: {
'Authorization': token
}
})
.then(page => page.json())
.then(pageJson => dispatch({ type: LOAD_SUCCEDED, payload: pageJson }))
.catch(issue => dispatch({ type: LOAD_FAILED, issue }));
}
Thank you!