- Intro
I'm creating a chat application and was trying to get rid of useless messages rerenders in a messages list when requesting them with onValue from firebase (snapshot from onValue was overwriting whole messages list state which caused to a rerender of all messages). Solution i came up with is to use onChildAdded which
is triggered once for each existing child and then again every time a new child is added
I created a useList hook which supposed to return a list of messages on every change of chatId (when switching between chats)
export function useList(chatId) {
const [list, setList] = useState([]);
const database = getDatabase();
useEffect(() => {
setList([])
const chatQuery = query(
ref(database, "/messages/" chatId),
orderByValue("timestamp")
);
onChildAdded(chatQuery, (data) => {
setList((prev) => [...prev, { key: data.key, message: data.val() }]);
});
}, [chatId]);
return [list];
}
And it works fine, but... :(
- Problem
When i'm changing chatId few times (was in a chat with person A, then switched to the person B, and then back to A) and sending message to A, this last message is displayed twice in a list of messages with a two children with the same key error.
Moreover if im switching between chats more then twice, and then sending a message, the state of this last message seems to be accumulating, and it is displayed as many times as there were switches between chats. All messages are sended to the firebase properly, after page reload everything is fine, no duplicates of last message, only after switching between chats.
- Conclusion
I'm fairly new in a react and firebase so, if there is some better way of solving all messages rerenders i would be happy to know about it. Also i would really appreciate explanation of this bug and it's solution :)
My Messages list
function Messages() {
const chatId = useSelector((state) => state.chat.id);
const [messages] = useList(chatId);
return (
<VStack w="100%" h="100%" p="5" overflowY="scroll" spacing="20px">
{messages.map(({ key, message }) => {
return <Message key={key} message={message}/>;
})}
</VStack>
);
}
4 Solution
useEffect(() => {
setList([]);
onChildAdded(chatQuery, (data) => {
setList((prev) => [...prev, { key: data.key, message: data.val() }]);
});
return () => off(chatQuery, 'child_added');
}, [chatId]);
CodePudding user response:
Once you call onChildAdded
for a query/path that callback will be called for new messages until you explicitly unsubscribe from updates. Since you only ever call onChildAdded
, you're adding more and more subscriptions.
So you'll want to stop the listener as shown in the documentation on detaching listeners, or by calling the unsubscribe function that onChildAdded
returns. If I remember correctly how lifecycle management in useEffect
hooks works, you can return the function that is used to unsubscribe. So it could be as simple as returning the function that you get back from onChildAdded
.