firebase-js-sdk
firebase-js-sdk copied to clipboard
Firebase 9 Database: set & update crash when onValue is listening
Firebase 9 Database: set & update crash when onValue is listening
- Operating System version: Ubuntu 20.04.5 LTS
- Browser version: Google Chrome Version 107.0.5304.87 (Official Build) (64-bit)
- Firebase SDK version: 9.15.0
- Firebase Product: database
- Vue 3
The problem is as follows
Im creating a simple chat app with firebase real time database. My simple app needs to write messages to a chat room and listen to any new message to render in the view. When the App starts, it automatically send the first message saying that the user is in. It the starts a listener with onValue, Everything goes fine. But when sending the next message, the message doesn't reach the database, and in the console I see the following stack
runtime-core.esm-bundler.js:220 Uncaught TypeError: newChildren.insert is not a function
at IndexMap.ts:147:30
at map (obj.ts:50:21)
at Proxy.addToIndexes (IndexMap.ts:115:24)
at Proxy.updateImmediateChild (ChildrenNode.ts:156:38)
at IndexedFilter.updateChild (IndexedFilter.ts:94:19)
at viewProcessorApplyUserOverwrite (ViewProcessor.ts:494:51)
at viewProcessorApplyOperation (ViewProcessor.ts:103:22)
at viewApplyOperation (View.ts:213:18)
at syncPointApplyOperation (SyncPoint.ts:107:9)
at syncTreeApplyOperationHelper_ (SyncTree.ts:730:9)
If I remove the onValue listener, all sent messages reach my database. although I cant get the updates by then.
Steps to reproduce:
- Preparing my database in Firebase.js
import { initializeApp } from "firebase/app";
import {getDatabase, ref, onValue, push, set, update, get, } from "firebase/database";
const config = {
XXXXXXXXXX
};
const firebase = initializeApp(config);
const database = getDatabase(firebase);
export { database, ref, onValue, push, set, update, get };
- Importing and using firebase
import { database, ref, onValue, push, set, update } from "../Firebase";
import router from "../router";
export default {
name: "Chat",
props: ["roomid", "roomname", "nickname"],
data() {
return {
message: "",
chats: [],
errors: [],
offStatus: false,
chatsRef: ref(database, "chatrooms/" + this.roomid + "/chats"),
};
},
created() {
this.sendMessage(this.nickname + " amejiunga katika redio.", "join");
onValue(this.chatsRef, (snapshot) => {
if (snapshot.exists()) {
this.chats = [];
snapshot.forEach((doc) => {
let item = doc.val();
item.key = doc.key;
this.chats.push(item);
});
}
});
},
unmounted() {
this.exitChat();
},
methods: {
onSubmit(evt) {
evt.preventDefault();
this.sendMessage(this.message, "newmsg");
this.message = "";
},
sendMessage(message, type = "newmsg") {
const newPostKey = push(this.chatsRef).key;
// const updates = {};
// updates[newPostKey] = {
// type: type,
// user: this.nickname,
// message: message,
// sendDate: Date(),
// };
set(ref(database, "chatrooms/" + this.roomid + "/chats/" + newPostKey), {
type: type,
user: this.nickname,
message: message,
sendDate: Date(),
});
// update(this.chatsRef, updates);
},
exitChat() {
this.sendMessage(this.nickname + " ametoka katika redio.", "exit");
this.offStatus = true;
router.go(-1);
},
},
};
Down grading firebase did'nt help anything. The same error occurred
Any idea/workaround?
Im stuck here.
I downgraded to 8.2.3
In sendMessage
, is this.roomid
defined? If it's undefined, then we throw that error.
In
sendMessage
, isthis.roomid
defined? If it's undefined, then we throw that error.
It's defined. Its in props
and it comes from another component to which the current component is attached
I've resolved the error. And now its working. The problem occurred because of using a single database reference for both listening and writing data.
import { database, ref, onValue, push, set, update } from "../Firebase";
import router from "../router";
export default {
name: "Chat",
props: ["roomid", "roomname", "nickname"],
data() {
return {
message: "",
chats: [],
errors: [],
offStatus: false,
chatsRef: ref(database, "chatrooms/" + this.roomid + "/chats"), // Here assigning the reference
};
},
created() {
this.sendMessage(this.nickname + " amejiunga katika redio.", "join");
onValue(this.chatsRef, (snapshot) => { // Here using it to listen
if (snapshot.exists()) {
this.chats = [];
snapshot.forEach((doc) => {
let item = doc.val();
item.key = doc.key;
this.chats.push(item);
});
}
});
},
unmounted() {
this.exitChat();
},
methods: {
onSubmit(evt) {
evt.preventDefault();
this.sendMessage(this.message, "newmsg");
this.message = "";
},
sendMessage(message, type = "newmsg") {
const newPostKey = push(this.chatsRef).key; // Here using it to push
// const updates = {};
// updates[newPostKey] = {
// type: type,
// user: this.nickname,
// message: message,
// sendDate: Date(),
// };
set(ref(database, "chatrooms/" + this.roomid + "/chats/" + newPostKey), {
type: type,
user: this.nickname,
message: message,
sendDate: Date(),
});
// update(this.chatsRef, updates);
},
exitChat() {
this.sendMessage(this.nickname + " ametoka katika redio.", "exit");
this.offStatus = true;
router.go(-1);
},
},
};
I'm not sure why this should be a problem with Firebase internals. I think this is a bug
Interesting. The only way I was able to reproduce this was to set this.roomid
to undefined, but let me try passing in the values as props.
Interesting. The only way I was able to reproduce this was to set
this.roomid
to undefined, but let me try passing in the values as props.
Since the undefined this.roomId
is also part of reference (ref(database, "chatrooms/" + this.roomid + "/chats")
) I guess that's an issue with db reference
I was able to reproduce the issue with create-vue, but downgrading to 8.2.3 didn't seem to work for me.
And after investigating some more today, it seems like the issue has something to do with the interaction between vue's proxying of RTDB and that object interacting with the SDK's, as when I created an object outside of the vue context and used that in vue, everything worked fine.
Are you using the same CLI tool? And the latest version of it?
I created my vue project with create-vue and then installed firebase with npm i firebase
but downgrading to 8.2.3 didn't seem to work for me.
The same to me
In the meantime, I would recommend you take a look at the official vue bindings for Firebase
This problem seems to be related to the Vue or Pinia/Vuex state. I managed to solve the problem by keeping the reference in an external variable that receives events and updates data by taking it out of state and putting it outside my defineStore
@store
state: () => ({
messagesRef : {}
...
this.messagesRef.on('child_added', (snapshot: any) => {} // receiving
...
this.messagesRef.push(payload.message); // pushing error
})
instead:
let messagesRef = {} as any;
export const firebaseStore = defineStore('x', {
... do the same ...
}
I initially wrote this comment, and was about to post a short reproduction:
This is a persistent bug even when you use VueFire.
You can set
or update
a database location, and then use $databaseBind to bind to it, no problem.
But if you set
or update
a database location that is already $databaseBind'ed, you will get these hard-to-understand errors, e.g.
VM6601 chunk-3G7ZM6PE.js:4031 Uncaught (in promise) TypeError: newChildren.remove is not a function at VM6601 chunk-3G7ZM6PE.js:4031:37 at map (VM6602 chunk-R5DULB7A.js:608:21) at Proxy.addToIndexes (VM6601 chunk-3G7ZM6PE.js:4008:24) at Proxy.updateImmediateChild (VM6601 chunk-3G7ZM6PE.js:4128:38) at Proxy.updateChild (VM6601 chunk-3G7ZM6PE.js:4567:19) at viewProcessorApplyUserOverwrite (VM6601 chunk-3G7ZM6PE.js:6508:51) at viewProcessorApplyOperation (VM6601 chunk-3G7ZM6PE.js:6356:23) at viewApplyOperation (VM6601 chunk-3G7ZM6PE.js:6725:18) at syncPointApplyOperation (VM6601 chunk-3G7ZM6PE.js:6775:30) at syncTreeApplyOperationHelper_ (VM6601 chunk-3G7ZM6PE.js:7128:30)
The database update does occur. The error occurs in the part of the code that is watching for changes.
I am on: "@vue/compat": "^3.2.47", "firebase": "^10.4.0", "vue": "^3.2.47", "vuefire": "^3.1.17"
BUT THEN
I found I could not reproduce it in a short program that does exactly what I described above. So now I know it is something more subtle. I will report back when I find out what subtlety is needed to trigger the error.
Hi all, as a workaround, are you able to use the function toRaw
? In your example @shija-ntula, I was able to call toRaw
on this.chatsRef
to bypass the Vue Proxy and it seemed to fix the issue:
import { defineComponent, toRaw } from "vue";
...
onValue(toRaw(this.chatsRef), (snapshot) => {
...
@maneesht this is still happening even if we use toRaw
method
@maneesht this is the log i put in node_module. Here i am getting newChildren as empty. This happens when i use listener like onChildChanged.
I have used different DB reference variable for this listener.
Firebase version: 10.7.2
Vue: 3.4.19
Product: Realtime database
Are there any scenarios wherein newChildren object can be empty?
we had a workaround to fix this issue. In our application, we had multiple DB listeners which used same firebase DB reference that was causing the issue. Hence, we created different DB references for each listeners like onChildChanged, onchildAdded, onValue etc. Eg: ` this.firebaseChatListListener = db
this.firebaseChatRoomListener = db
onChildAdded(ref(this.firebaseChatListListener, ${path}), callback)
onValue(ref(this.firebaseChatRoomListener, ${path}), callback) `
Solution
I have solved this problem by writing a small helper function. I replace all calls to firebase set
(), with calls to my function setClean
()
import {set,ref} from "firebase/database";
export function setClean(firebaseRef, valueMaybeUndefined) {
// _ _ _ _
// /| |_ o _|_ |_ _ |_) |_ |_
// | o | | >< |_ | | (/_ | \ |_ |
// Because it may be a Vue Proxy, which causes an error if you are also
// listening at that firebase location, we must reconstitute the
// reference from the path.
const targetPath = firebaseRef._path.pieces_.join("/");
const targetRef = ref(db, targetPath);
// _ _ _
// ) |_ o _|_ |_ _ \ / /\ | | | |_
// /_ o | | >< |_ | | (/_ \/ /--\ |_ |_| |_
// Because we are not allowed to write undefined to Firebase,
// we convert it to null.
const valueNeverUndefined =
valueMaybeUndefined === undefined ? null : valueMaybeUndefined;
console.log("setClean", targetPath, valueNeverUndefined);
return set(targetRef, valueNeverUndefined);
}
So if my code initially said:
import {set} from "firebase/database";
...
set(x,y)
I now say
import {setClean} from .... my helper function as shown above ...
...
setClean(x,y)
Same issue for me. Can't set/remove while having onValue working in my JS react webapp. Workaround with two "db" doesn't help - or what exactly is the way to initialize two different db objects? "firebase": "^10.12.2"