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
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.
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?
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
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);
});
...
}, []);