Update only specific data in Realtime database of Firebase v9 - javascript

I am new to firebase and using ReactJs, I am wondering how can I update a specific data in Realtime database, I want to change the value of it to true or false interchangeably.
This is my code.
const [status, setStatus] = useState(false);
const [tempId, setTempId] = useState("");
const [todo, setTodo] = useState("");
const updateStatus = () => {
update(ref(db, `/${auth.currentUser.uid}/${tempId}`), {
completed: setStatus(!status),
todo: todo,
id: tempId,
});
};
Thank you in Advance.

If you only want to update the status property, you can do:
update(ref(db, `/${auth.currentUser.uid}/${tempId}`), {
completed: !status
});
Since your calling update, it will leave the other properties of the node unmodified.
Alternatively, you can do:
update(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), !status)
Or:
set(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), !status)
Both of these write to the complete path of just the status property.
What you'll note is that I don't call setStatus in the code above. Instead I recommend listening to the value of the status node, and then calling setStatus when you get a callback there:
onValue(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), (snapshot) => {
setStatus(snapshot.val());
});
Separating the read and write operations like this is known as command query responsibility segregation (or CQRS) and in the case of Firebase won't even be slower for the user, as the local SDK will immediately call the onValue handler when you call set or update on that path.

Related

Why doesn't Firestore getDocs work after the page is refreshed with no error in console?

I'm using what seems to be standard code to get all docs from Firestore and populate my localStorage with results from 2 collections, items and ratings.
It works initially.
Only thing is the page stops working once it's refreshed, no errors in the console. It can't be a firebase error, I just want it to get items from the db. The page doesn't need to be refreshed but I need to design for the possibility that the user might do so.
Why does the code only work once? Is it something to do with the await part of the querySnapshot?
// ----------> firebase vars <----------------------------------
initializeApp(firebaseConfig);
export const db = getFirestore();
export const auth = getAuth();
export const colRefItems = collection(db, 'items');
export const colRefRating = collection(db, 'ratings');
// ----------> Sync - get VARS from firebase <----------------------------------
const lastUpdated = settings[0].firebase.updated;
const user = "xxxxxxxxx";
console.log('%cSYNC: Pre items load', 'color:#059f2d')
// ----------> Sync - get items from firebase <----------------------------------
const q2 = query(colRefItems,
where("created", ">=", lastUpdated),
where("user", "==", user),
orderBy("created")
)
const querySnapshot = await getDocs(q2).then().catch((e)=> console.log(e));
console.log('%cSYNC: After items await', 'color:#059f2d')
querySnapshot.forEach((doc) => {
j++; let dataJk = {
uid: doc.id, type: +doc.data().type, user: user, colour: +doc.data().colour, created: doc.data().created, desc: doc.data().desc
}; itemListSync.push(dataJk)
});
if(j>0)localStorage.setItem('items',JSON.stringify(itemListSync));console.log('%cSYNC: '+itemListSync.length+' item(s) synced from Firestore', 'color:#059f2d')
console.log('%cSYNC: After items loop', 'color:#059f2d')
// ----------> Sync - get ratings from firebase <----------------------------------
const q3 = query(colRefRating,
where('created', '>=', lastUpdated),
where("user", "==", user),
orderBy('created')
)
const querySnapshotRating = await getDocs(q3).then().catch((e)=> console.log(e));
console.log('%cSYNC: After ratings await', 'color:#059f2d')
querySnapshotRating.forEach((doc) => {
k++; let ratingData = {
uid: doc.id, rating: +doc.data().rating, created: doc.data().created, user: user
}; itemRatingSync.push(ratingData)
});
if(k>0)localStorage.setItem('ratings',JSON.stringify(itemRatingSync));console.log('%cSYNC: '+itemRatingSync.length+' rating(s) synced from Firestore', 'color:#059f2d')
console.log('%cSYNC: After ratings loop', 'color:#059f2d')
You need to use useEffect and setState to be able to render your data. you need to use an Asynchronous function to fetch data from Firestore. This can be achieved using two ways , one is using the getDocs() method which is a method to get the data once, other is onSnapshot() this sets a listener to receive data-change events which means Cloud Firestore will send your listener an initial snapshot of any changes to your documents, collections of documents, or the results of queries. See sample code below on how to implement the onSnapshot() method , your code probably seems to have an issue with sequence of calling the data and render function, which causes this issue, check the code for these ,you may pass log statements to see what is called first and then rectify the same.
useEffect(() => { const getLabels = async () => { const colRef = query(collection(db, '<collection-name>')) onSnapshot(colRef, (snapshot) => { setLabels(snapshot.docs.map(doc => ({ id: doc.id, data: doc.data() }))) }) } getLabels() }, [])
Also check the following examples of similar implementations:
Problem with rendering userdata
Firestore not loading data in Useeffect
Issue rendering data in firestore

Why changes made in database not updates in realtime?

I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.
Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId

Not getting data from firebase on opening the app

I am trying to get data from firebase but it returns empty value when the app loads, but if I edit something on that file even the commented line, then the data loads and app runs, I want when the app opens all data should be there from firebase to run app. and also how to arrange "grabbedData" in reverse order tried grabbedData.reverse() but doent work.
const Getdata = async () => {
let grabbedData = [];
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
// console.log("snapshot....", snapshot);
grabbedData.push(snapshot.val());
});
setUserdata(grabbedData);
console.log("grabbedData", grabbedData); // empty value here :(
if (grabbedData) {
let getfranchiseuid = "";
Object.keys(grabbedData).map(function (key) {
let y = grabbedData[key];
Object.keys(y).map(function (key2) {
let x = y[key2];
if (key2 === uid) {
getfranchiseuid = x.franchiseuid;
}
});
});
if (getfranchiseuid) {
let customerList1 = [];
firebase
.database()
.ref(`/serviceProvider/${getfranchiseuid}/franchise/customers`)
.orderByKey()
.on("value", (snapshot) => {
customerList1.push(snapshot.val());
});
setCustomerList(customerList1);
console.log("customerList1customerList1", customerList1);
}
}
};
useEffect(() => {
var unsubscribe = firebase.auth().onAuthStateChanged(function (user) {
if (user) {
storeUser({ user });
setUser(user);
setEmail(user.email);
setUid(user.uid);
} else {
// No user is signed in.
}
});
unsubscribe();
Getdata();
}, []);
Data is loaded from Firebase asynchronously. Since this may take some time, your main JavaScript code will continue to run, so that the user can continue to use the app while the data is loading. Then when the data is available, your callback is invoked with that data.
What this means in your code is that (for example) right now your setUserdata is called before the grabbedData.push(snapshot.val()) has run, so you're setting any empty user data. You can most easily see this by setting some breakpoints on the code and running it in a debugger, or by adding logging and checking the order of its output.
console.log("1");
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
console.log("2");
});
console.log("3");
When you run this code, the output will be:
1
3
2
This is probably not what you expected, but it is exactly correct and does explain your problems.
The solution for this is always the same: any code that needs the data from the database must be inside the callback, or be called from there.
So for example:
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
grabbedData.push(snapshot.val());
setUserdata(grabbedData);
});
this will ensure that setUserdata is called whenever you updated the grabbedData.
Since you have much more code that depends on grabbedData, that will also have to be inside the callback. So the entire if (grabbedData) { block will need to be moved, and probably others. If you keep applying the solution above, the code will start working.
This is a very common problem for developers that are new to calling asynchronous cloud APIs, so I highly recommend reading some of these other answers:
Why Does Firebase Lose Reference outside the once() Function?
Best way to retrieve Firebase data and return it, or an alternative way
How do I return the response from an asynchronous call? (this one is not specific to Firebase, as the problem is not specific to Firebase)

React state resetting

I have a basic component which subscribes to an EventEmitters event. All it is meant to do, is accumulate an array of messages.
What is happening however, is the message array only ends up containing the latest message.
Here's the component source:
export const MessageList = ({serverId, connection}) =>
{
var [messages, setMessages] = useState([]);
const appendSystem = (message) =>
{
console.log("Appending " + message);
console.log(messages);
setMessages([...messages, message]);
}
useEffect(() =>
{
connection.on('system', appendSystem);
return () => connection.removeListener('system', appendSystem);
},
[serverId]);
console.log("Rendering");
console.log(messages);
}
The output I'm getting however...
Rendering
[]
Appending Permission granted. Attempting connection.
[]
Rendering
["Permission granted. Attempting connection."]
Appending Connection Succeeded
[]
Rendering
["Connection Succeeded"]
So it seems each time appendSystem is called, messages is an empty array. Therefore, setMessages is always appending the new message to an empty array.
Does anyone know what could be causing this?
I'm under the impression appendSystem is for some reason caching 'messages' at the start, and reusing the original value, but have no idea how I would go about debugging or fixing that.
This is a common issue with React hooks' non-intuitive state setting mechanism.
Try using setMessages with a function as its argument, rather than with the new value. This will guarantee you use its most recent value:
setMessages(prevMessages => [...prevMessages, message]);

Store data from useQuery with useState

I'm using React hooks both to fetch GraphQL data with react-apollo and to store local state:
const [userData, setUserData] = useState({})
const { loading, error, data } = useQuery(USER_QUERY)
However, I'm wondering how to store data to userData. Is this how it's supposed to work:
useEffect(() => {
setUserData(data)
}, [Object.entries(data).length])
Looks like what you have probably works. There is also a onCompleted option available in the options parameter. it takes a callback of type:
(data: TData | {}) => void
so this is another way of doing it:
const { loading, error, data } = useQuery(USER_QUERY, {onCompleted: setUserData})
What are you trying to do with the returned data that you are unable to accomplish by simply using it as destructured from the query hook? In most use cases it can be used immediately, as it will update itself when refetched.
If it is necessary (and it could be), as the other answer says, the useEffect hook you posted should work, but I would replace the dependency with simply data, to prevent an edge case where the response has an equal length consisting of different data and does not update:
useEffect(() => {
setUserData(data)
}, [data])
I think something like this would work - you will need to create the initial state with useState, could be empty array and then onComplete in the useQuery would setTranscationsData... it is triggered every render when state or props change. Could of course add an inital state inside useState which insn't an empty array.
const [transactionsData, setTransactionsData] = React.useState([]);
const { error, data } = useQuery(GET_TRANSACTIONS, {
onCompleted: () => {
setTransactionsData(data.transactions);
},
});
another example
const [getLegalStatement] = useLazyQuery(GET_LEGAL_STATEMENT, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
setTempLegalStatement(data.getLegalStatement);
},
onError: () => {
setTempLegalStatement({
consentedLegalStatementHash: '',
consentedSuppliersHash: '',
statement: '',
suppliersModal: '',
});
setTimeout(() => {
setRefetchNeeded(true);
}, 10000);
},
});
Use onSuccess
const [userData, setUserData] = useState({})
const { data, isLoading, error } = useQuery('QueryKey', QueryFunction, { onSuccess: setUserData })
This onSuccess callback function will fire setUserData(data) for you automatically any time the query successfully fetches new data.
To elaborate above, you can't use onSuccess/onSettled because those will not rerun if the data is cached, so if you leave the component and come back before the query expires your data won't get set.

Categories

Resources