React native - how to refresh screen when message has been sent? - react-native

so currently I have 2 simulators open. Each simulator is logged in as a different user. When I enter the chat between these 2 users, messages are getting sent live in the socket which is perfect.
My problem:
Example: if user 1 is on the all messages screen and user 2 is inside the chat. And user 2 sends user 1 a message, user 1's screen does not automatically update with the new message, I need to either scroll to refresh or navigate from one page to the other.
How should I implement the when a new message is sent it gets shown to user 1?
Here is my code:
AllMessagesScreen.js
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const[page,setPage]=useState(0);
const [refreshing, setRefreshing] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await messagesApi.getMessages();
setLoading(false);
if(refreshing) setRefreshing(false);
if (!response.ok) return setError(true);
setError(false);
setPosts(response.data)
};
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
loadPosts();
});
return unsubscribe;
}, [navigation]);
return(
<FlatList
data={posts}
keyExtractor={(listing) => listing.id.toString()}
renderItem={({ item,index }) => (
<MessagesList
title={item.Post.title}
subTitle={item.Messages[0].message}
onPress={() => navigation.navigate(routes.CHAT,{message:item,index})}
/>
)}
ItemSeparatorComponent={
ListItemSeparator
}
refreshing={refreshing}
onRefresh={() => {
loadPosts()
}}
/>
In other words, when a message is sent in the chat screen I want my allmessages screen to re-render, currently it is only getting render when I scroll to refresh or navigate to allmessages screen.
If you require any additional information please tell me, I will provide it immediately. Thank you
chat screen.js
function ChatScreen({route,navigation}) {
const message = route.params.message;
const [messages, setMessages] = useState([]);
const [refreshing, setRefreshing] = useState(false);
const [text, setText] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [socket, setSocket] = useState(null);
const { user } = useAuth();
const index = route.params.index;
const updateView = route.params.updateView;
useEffect(() => {
const newsocket =io.connect("IPADDRESS")
setMessages(message.Messages)
newsocket.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation ${message.id}`)
setSocket(newsocket)
newsocket.emit('subscribe', message.id);
});
newsocket.on("send_message", (msg) => {
console.log("this is the chat messages:", msg);
setMessages(messages => [msg, ...messages]);
});
return(()=>newsocket.close());
}, []);

Let's say you have the groups schema like this:
{
id: string;
name: string;
}
and messages schema like this:
{
id: string;
groupId: string;
text: string;
}
Now to recognise that a new message has been sent in a group you can add lastMessage in group schema, like this:
{
id: string;
name: string;
lastMessage: Message;
}
This way your whenever a new message is sent, respective group will change, which will make the web-socket emit an event and you will get updated data.
In you db in lastMessage field you can have foreign key to message collection, but when sending the json to frontend send the whole message object.
Also you will have to update your function for message creation to update the group too with lastMessage.
If you have creation time in messages schema, having lastMessage in group will help with sorting them too, like showing group with latest message on top.

Related

Problem rendering flatlist data with api sauce

So i'm building a very simple mobile app that fetches clients data .so far everything is working fine. The only problem i keep encountering is rendering Flatlist data using api sauce. My api call is successful, i receive all the data from the database. When i reload expo the data doesn’t show yet when i hit save ‘cmd + s’ of my code the data get displayed successfully displayed. So i don’t understand why my data doesn’t get rendered when the component get mounted. here's my code
// i built hook for my api calls
import useApi from "../../hooks/useApi";
const getFoldersApi = useApi(foldersApi.getFolders);
const [foldersList, setFoldersList] = useState([]);
useEffect(() => {
getFoldersApi.request();
setFoldersList(getFoldersApi.data);
}, []);
<FlatList
style={styles.folders}
/// here reside the problem
data={foldersList}
keyExtractor={(folder) => folder.Guid}
renderItem={({ item, index }) => (
<Card
title={item.Nom}
image={folderIcon}
index={index}
folderType={item.TypeDossier}
folderState={item.EtatDossier}
depositDate={item.DatedepoDossier}
shippingType={item.TypeManifeste}
vendor={item.Fournisseur}
onPress={() => navigation.navigate("Détails", item)}
/>
)}
/>
my api hook
import { useState } from "react";
export default useApi = (apiFunc) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const request = async (...args) => {
setLoading(true);
const response = await apiFunc(...args);
setLoading(false);
if (!response.ok) return setError(true);
setError(false);
setData(response.data);
};
return { data, error, loading, request };
};
here's what i mean if any of you didn't understand my problem

useQuery renders when state changes

I want to useQuery renders whenever the state changes
is there any option in useQuery hook
`export const Search = ({ navigation }) => {
const [search, setSearch] = useState();
const [dismiss, setDismiss] = useState(false);
const [searchResult, setSearchResult] = useState();
const searchHander = (query) => {
setSearch(query)
setDismiss(true)
}
const searching = useQuery(['searching', search], () => api.search(search));
useMemo(() => {
setSearchResult(searching?.data ? searching?.data?.results : []);
}, [searching?.data])
const searchResults = ({ item }) => {
return <View style={{ marginVertical: 10 }}><SearchResults navigation={navigation} data={item} /></View>
}
const desmiss = useRef(null);
return (...)}`
useQuery is not depend to state
I don't fully understand the question:
I want to useQuery renders whenever the state changes
I'm just assuming you want to refetch when the state changes, because when state changes, your component does render.
For this, all you need to do is add search to the queryKey, which you have already done.
Further, I can see that you are calling setSearchResults in useMemo which is a) unnecessary because react-query already gives you the result and b) violates component purity because you call setState during render.
I think the component should just be:
const [search, setSearch] = useState();
const [dismiss, setDismiss] = useState(false);
const [searchResult, setSearchResult] = useState();
const searchHander = (query) => {
setSearch(query)
setDismiss(true)
}
const searching = useQuery(['searching', search], () => api.search(search));
const searchResult = searching?.data?.results ?? []
Then you can work with searchResult, and whenever setSearch is called, your query will refetch.

How to receive one callback from three simultaneous events in React Native?

I'm listening to three different events that happen simultaneously and I want to get some data from each of these events. However, I want to receive the latest data from all three events at once.
I tried using useEffect but, of course, this is triggering the callback at least three times, instead of just once.
const [key, setKey] = useState('');
const [text, setText] = useState('');
const [position, setPosition] = useState(0);
const onKeyPress = ({ nativeEvent: { key } }) => setKey(key);
const onChangeText = text => setText(text);
const onSelectionChange = ({ nativeEvent: { selection: { start, end } } }) => {
start === end && setPosition(start);
}
useEffect(() => {
// I want to do stuff with the latest key, text and position.
// However, this is called more than once when typing.
}, [key, text, position]);
// ..
<TextInput
onChangeText={onChangeText}
onKeyPress={onKeyPress}
onSelectionChange={onSelectionChange}
/>
How can I achieve this?

react native - why is my console.log returning [] but items get rendered on screen?

I am trying to access the object obtained from my API get request but I keep getting Array[] returned in the console.log while the items get rendered on the screen.
Can someone spot where I went wrong?
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await messagesApi.getMessages();
setLoading(false);
if (refreshing) setRefreshing(false);
if (!response.ok) return setError(true);
setError(false);
setPosts(response.data);
};
useEffect(() => {
const newsocket = io.connect("http://ip:port");
loadPosts();
console.log(posts); // not working
newsocket.on("connect", (msg) => {
setSocket(newsocket);
});
return () => newsocket.close();
}, []);
return (
<FlatList
data={posts}
keyExtractor={(post) => post.id.toString()}
renderItem={({ item, index }) => (
<MessagesList
title={item.title}
onPress={() =>
navigation.navigate(routes.CHAT, { message: item, index, updateView })
}
/>
)}
/>
);
ISSUE
console.log executes before getting an API response.
SOLUTION
console.log would work when you add posts in dependency like
useEffect(() => {
console.log(posts);
}, [posts]); // added posts here

React native: Flatlist does not update instantly when i send a message

I just finishing setting up my socket in my react native and nodejs project and still my flatlist does not update instantly when a message is sent, i need to refresh the app in order for it to update.
I thought that by using socket this will work but still it is not working. whenever i user opens a chat i get eg. user: 1 has joined conversation 1 in the console which indicates that the socket is working.
Client Side
function ChatScreen({route,navigation}) {
const message = route.params.message;
const [messages, setMessages] = useState(message.Messages);
const [text, setText] = useState('');
const [socket, setSocket] = useState(null);
const { user } = useAuth();
const index = route.params.index;
const updateView = route.params.updateView;
useEffect(() => {
const newsocket =io.connect(socketURL)
setMessages(messages);
newsocket.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation ${message.id}`)
setMessages(messages=>messages.concat(msg))
setSocket(newsocket)
});
return()=>newsocket.close;
}, []);
const onSend = (ConversationId,senderId,receiverId,message) => {
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
setText("")
const to = (user.id===route.params.message.user1?
route.params.message.user2:route.params.message.user1)
socket.emit('message', { to: to, from: user.id, message, ConversationId });
};
const updateText=(text)=>{
setText(text);
}
return (
<FlatList
inverted
data={message.Messages}
keyExtractor={(message) => message.id.toString()}
renderItem={({item,index})=>(
<>
<Text>
{moment(item.createdAt).fromNow()}
</Text>
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
</>
)}
/>
<View style={styles.messageBoxContainer}>
<TextInput
onChangeText={updateText}
value={text}
/>
<TouchableOpacity
onPress={()=>{onSend(message.id,user.id,(user.id===message.user1?
message.user2:message.user1),text)}}>
</TouchableOpacity>
</View>
);
}
Server Side
const express = require("express");
const app = express();
const http = require("http");
const socket = require("socket.io")
const server=http.createServer(app);
const io =socket(server)
io.on('connection', (socket) => {
console.log("connected")
socket.on('message', (data) => {
console.log(data)
socket.join(data.ConversationId);
io.sockets.in(data.to).emit('send_message', { message:
data.message, to: data.to });
});
});
UPDATE
Client Side
const message = route.params.message;
const [messages, setMessages] = useState([]);
const [text, setText] = useState('');
const [socket, setSocket] = useState(null);
const { user } = useAuth();
useEffect(() => {
const newsocket =io.connect("http://192.168.1.103:9000")
newsocket.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation
${message.id}`)
setSocket(newsocket)
setMessages(message.Messages)
});
newsocket.on("send_message", (msg) => {
console.log("this is the chat message:", msg);
setMessages([ { ...message.Messages },...messages]);
});
return()=>newsocket.close;
}, []);
const onSend = (ConversationId,senderId,receiverId,message) => {
console.log("sent")
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
setText("")
const to = (user.id===route.params.message.user1?
route.params.message.user2:route.params.message.user1)
socket.emit(
'message', { to: to, from: user.id, message,ConversationId });
};
const updateText=(text)=>{
setText(text);
}
<FlatList
inverted
data={messages}
keyExtractor={(message) => message.id.toString()}
renderItem={({item,index})=>(
<>
<Text>
{moment(item.createdAt).fromNow()}
</Text>
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
</>
)}
bounces={false}
/>
<View style={styles.messageBoxContainer}>
<TextInput
onChangeText={updateText}
value={text}
/>
<TouchableOpacity
onPress={()=>{
onSend(
message.id,
user.id,
(user.id===message.user1?message.user2:message.user1),
text
)}}
>
<Text>Send</Text>
</TouchableOpacity>
</View>
Server Side
io.on('connection', (socket) => {
console.log("connected")
socket.on('message', (data) => {
console.log(data)
socket.emit('send_message', { message: data.message, receiverId:
data.to,senderId:data.from,conversationId:data.ConversationId })
});
});
Using the updated code, when i open a chat i get
user: 43 has joined conversation 4 ---- on client side console
connected ---- on server side console
Using the updated code, when i send a message i get
this is the chat message: Object {
"conversationId": 25,
"message": "You",
"receiverId": 47,
"senderId": 43,
} --- in my client side console
{ to: 47, from: 43, message: 'You', ConversationId: 25 } ---- server
side console
But then i get an error
undefined is not an object (evaluating 'message.id.toString')
I think my problem is that i am not emitting the the message id correctly and therefore my flatlist does not know it. To get a message id, i need to store the message in db first
NEW UPDATE
Client Side
const message = route.params.message;
const [messages, setMessages] = useState([]);
const [text, setText] = useState('');
const [socket, setSocket] = useState(null);
const { user } = useAuth();
useEffect(() => {
const newsocket =io.connect(socketURL)
newsocket.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation
${message.id}`)
setSocket(newsocket)
setMessages(message.Messages)
});
newsocket.on("send_message", (msg) => {
console.log("this is the chat message:", msg);
const data = [...messages];
console.log(data)
data.push(msg);
setMessages(data);
});
return(()=>newsocket.close());
}, []);
const onSend = (ConversationId,senderId,receiverId,message) => {
console.log("sent")
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
setText("")
const to = (user.id===route.params.message.user1?
route.params.message.user2:route.params.message.user1)
socket.emit(
'message', { to: to, from: user.id, message,ConversationId });
};
const updateText=(text)=>{
setText(text);
}
<FlatList
inverted
data={messages}
keyExtractor={(item,index)=>index.toString()}
renderItem={({item,index})=>(
<>
<Text>
{moment(item.createdAt).fromNow()}
</Text>
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
</>
)}
bounces={false}
/>
<View style={styles.messageBoxContainer}>
<TextInput
onChangeText={updateText}
value={text}
/>
<TouchableOpacity
onPress={()=>{
onSend(
message.id,
user.id,
(user.id===message.user1?message.user2:message.user1),
text
)}}
>
<Text>Send</Text>
</TouchableOpacity>
</View>
Server Side
io.on('connection', (socket) => {
console.log("connected")
socket.on('message', (data) => {
console.log(data)
socket.emit('send_message', { message: data.message, receiverId:
data.to,senderId:data.from,conversationId:data.ConversationId })
});
});
Now using the new updated code when i send a message only the new message gets rendered to the sender without the previous messages, and the receiver does not receive anything while on the chat.
So from your answers I get the impression that in order to make it work you would need to do roughly the following:
On your client side you gotta have a socket listening for updates:
socket.on('send_message', (e) => doSomethingWith(e));
Once you have your socket listening to updates, you can treat the information received. The easiest way to make sure your component rerenders and shows the correct information is to use state. So upon receiving the message from the socket, you could set the messages
setMessages(socketMessage)
You then pass messages state to your data in Flatlist. I would also pass it in the extraData field also in Flatlist.
If you have redux in place, you may want to save the messages there, then there will be no need to set the local state in the component and you could just take it from props.
Hope this helps, if you have any additional questions don't hesitate to ask them.
Edit 1:
I'm just gonna copy the code from the solution you mentioned above:
useEffect(() => {
handleGetGroupMessage();
let socket = io(socketUrl);
socket.on("GroupChat", (msg) => {
console.log("this is the chat messages", chatMessages);
setChatMessages(chatMessages.concat(msg)); // this is the "doSomethingWith(e)"
});
}, []);
You receive your message via the socket and then have to do something with it. Not using redux? Set it to local state or to top level context, whatever works for your app.
Edit 2:
socket.on('connect', ...) is a standard event which socket understands. It's not your messages, you need to establish that apart.
Try this way
<FlatList
inverted
data={messages}
keyExtractor={(item,index) => index.toString()}
extraData={messages} // add this
renderItem={({item,index})=>(...)}
useEffect(() => {
...
newsocket.on("send_message", (msg) => {
console.log("this is the chat message:", msg);
// setMessages([ { ...message.Messages },...messages]); // remove this
// add this
const data = [...messages];
data.push(msg);
setMessages(data);
});
...
}, []);