I've a simple question but I can't find the answer.
I'm using Vuexfire to import data from Firebase in Vuex.
const state = {
ricettario: [] // data that contains all recipes (objects)
}
const actions = {
// Get data from Firebase
init: firestoreAction(({ bindFirestoreRef }) => {
bindFirestoreRef('ricettario', db.collection('ricettarioFirebase'))
}),
}
It works perfectly but I want to manipulate every document of the collection 'ricettarioFirebase'. With vue + firebase was easy, with .get() and .then()
I can't find a solution! I thought using GETTERS is the best way to do that but I'm new with Vuex and Vuexfire so I don't know how to do that.
In particular, I want to convert this (classic firebase command):
db.collection("ricettarioFirebase")
.orderBy("data")
.get()
.then(snapshot => {
snapshot.forEach(doc => {
let ricetta = doc.data();
ricetta.data = dayjs(ricetta.data)
.locale("it")
.format("D MMMM YYYY");
ricetta.diffClass = ricetta.diff.split(" ").join("_");
this.ricettario.push(ricetta);
});
});
In Vuexfire. So change every object in the "ricettario[ ]" will be "ricetta", and I want to edit the "ricetta.diffClass" and "ricetta.data"
As you will see in the Vuexfire documentation:
Vuexfire does not handle writing data back to Firebase because you can
directly use the Firebase JS SDK to precisely update whatever you
need.
The doc gives some example of use of the "standard" JS SDK, exactly like you did in your question ("classic firebase command"). So you'll have to go this way, wrapping the database writes in Vuex Actions.
Related
I am fairly familiar with invoking firebase firestore functions manually for testing. I have done this in the past with the docs here. In short I make a wrappedOnWrite variable, assign it to my admin sdk.wrap(my firebase function), and then I invoke it with an object that represents the firestore collection before and after the call. Example:
const testEnv = functions({ projectId: **** }, '****');
let wrappedOnWrite:
| WrappedScheduledFunction
| WrappedFunction<Change<QueryDocumentSnapshot>>;
beforeAll(() => {
// wrap the firebase function, ready to be invoked
wrappedOnWrite = testEnv.wrap(moderateCompetition);
});
const changeDoc: Change<QueryDocumentSnapshot> = {
before: testEnv.firestore.makeDocumentSnapshot(dataBefore, path),
after: testEnv.firestore.makeDocumentSnapshot(dataAfter, path),
};
// call firebase function with the changes inputted to the function
await wrappedOnWrite(changeDoc);
This is great for testing my firestore collections with jest, however, I am never seen this done with firebase storage, and I haven't been able to find many useful resources either. I have a pretty basic firestore .onFinalize function for storage:
export const blurImages = functions.storage.object().onFinalize(async (object) => {}
Is there any way to manually test it like in the example I gave above? When I initially deployed the function, it ran recursively and charged my company a couple of dollars, and I need to be able to run it periodically so that recession doesn't happen on the live function. Thanks in advance :)
I'm using quasar with firebase 9. I'm trying to set the state with actions during the boot of the project. I have data in firebase that I want to load only once and assign to an array in the Vuex state.
there might be a way in firebase 8 but I can't solve this problem in the newer version of firebase
import { getData } from "src/firebase/config";
// load data from packages collection
export const loadPackages = async () => {
const packages = await getData("packages");
return packages;
}
// load data from menu collection
export const loadMenu = async () => {
const packages = await getData("meals-menu");
return packages;
}
I use the return of each function in the respective vue component but I want to use the return in mutating the vuex state.
any tips?
thank you
as a work around solution; try saving data from firebase into localstorage then set vuex state after localstorage is set
firebase collection has event methods to allow exactly this
Look at this as starting point: https://firebase.google.com/docs/database/web/read-and-write#read_data_once_with_an_observer
Inside event handlers you have the 'realtime' changes/added/removed data
So you can change store
I'd like to implement Firestore offline persistence on my PWA React app using the reactfire library.
const firestore = useFirestore().enablePersistence();
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
but running the code i get an error:
FirebaseError: Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
This component is wrapped inside a <Suspense> as mentioned in the documentation
That database read is the only one that i make in the entire app, how can i solve?
Edit.
Using the example that #Ajordat gave, I've imported the preloadFirestore function inside the App component I do get an error:
"Cannot read property 'name' of undefined".
Whereas adapting (because I cannot use hooks inside the fetch function)
the example from #DougStevenson: I've imported useFirestore function in the App component (in order to get the Firestore object) to enable persistence, and then importing it (useFirestore) into my component in order to retrieve the data, but now, I get the same error as before,
Firestore has already been started and persistence can no longer be enabled.
Edit 2:
I've tried to enablePersistence without errors, thank guys, this is my approach, let me know if it is the best:
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
And in my custom component:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let document = useFirestoreDocDataOnce(docRef);
console.log(document)
But now I do have a problem, when I log the document, the data are not emitted instantly, yeah I know that it is an asynchronous operation, but the component is wrapped inside a <Suspense>, in this way:
<Suspense fallback={<div>Loading</div>}>
<FoodComponent foodName={"Milkshake"} />
</Suspense>
But I don't see the loading text before the component is actually rendered.
Does the suspense fragment show the fallback component only while is loading the function (useFirestore) and not the actual data?
Well, I've solved, have to destructure the data, doing like that:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document)
On other JavaScript libraries for Firestore, enablePersistence() returns a promise. That means it will complete some time in the future, with no guarantees how long it will take. If you're executing the query immediately after you call enablePersistence(), without waiting for the returned promise to become fulfilled, then you will see this error message. That's because the query "beats" the persistence layer and effectively executes first.
You will have to figure out how to use that promise to wait until it's OK to make that query with persistence enabled. For example:
seFirestore().enablePersistence()
.then(() => {
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
})
.catch(error => {
console.error("enablePersistence failed", error);
})
Notice how the query will complete only after the persistence is fully enabled.
Thanks for the suggestion guys #DougStevenson and #Ajordat
In app component:
import { useFirestore } from "reactfire"
...
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
In your custom component, where you want to use Firestore:
import { useFirestore, useFirestoreDocData /* or what you want to use */ } from "reactfire"
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document);
I'm trying to fetch real time data from Cloud Firestore using the below code.
export const getRealTimeData = () =>
db
.collection('posts')
.onSnapshot(
(querySnapshot) => {
const posts: any = [];
querySnapshot.forEach((doc) =>
posts.push(Object.assign({
id: doc.id
}, doc.data()))
);
},
);
};
And, I want to use the resultant array to display the data on UI. When I'm doing this, the resultant array is a function but not the actual array of data.
const posts = getRealTimeData();
Here's what I get when I log posts
function () {
i.kT(), o.al(s);
}
Could anyone please point where I went wrong?
Realtime listeners added with onSnapshot() are not compatible with returning values from function calls. That's because they continue to generate new results over time, and would never really "return" anything once. You should abandon the idea of making a synhronous getter type function in this case - they just don't work for what you're trying to do.
Ideally, you would use an architecture like Redux to manage the updates as they become available. Your realtime listener would dispatch query updates to a store, and your component would subscribe to that store that to receive those updates.
If you don't want to use Redux (which is too bad - you really should for this sort of thing), then you should wrap your query inside a useEffect hook, then have your listener set a state hook variable so your component can receive the updates.
Getting frustrated to solve this since I am no JS expert. 😢
I am using Firestore as a database and VuexFire to bind the data to VueX state, like so.
getLeads: firestoreAction(async ({
bindFirestoreRef
}) => {
// return the promise returned by `bindFirestoreRef`
return bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30))
}),
It gets the first 30 results and then i want to implement an infinite scroll feature to run a function every time the scroll reaches the bottom and fetch more data and bind to the same state. In Firestore pagination require passing a query cursor of the last fetched document as a reference
Below from firebase document, with vanilla JS
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
});
since I use VuexFire to bind the data to state, I dont see an option to get the snapshot of the last document fetched by VuexFire (lastVisible from the above code), in order to pass it to the next query.
Any help will be highly appreciated. 🙏🏽
Lets say I have a collection of Customer records and i am displaying the first 5 ordered by last updated.
The query is
getLeads: firestoreAction(({ commit, bindFirestoreRef
}) => {
bindFirestoreRef('Leads', db.collection('leads')
.orderBy('updated.date', 'desc').limit(5)).then(documents => {
commit('POPULATE_TESTLEADS', documents);
commit('LAST_DOC', documents[documents.length - 1]);
});
}),
I am saving both the results and the lastdoc in the state, looping and showing the names, like so:
Nakheel
Emaar Group
Yapi Kredi Inc
Cairo Amman Bank
Arab Jordan Investment Bank LLC
I then call again with the last doc as query cursor and expect the next 5 docs to return from firebase, like so
moreLeadLeads: firestoreAction(({ state, bindFirestoreRef
}) => {
bindFirestoreRef('testLeads', db.collection('leads')
.orderBy('updated.date', 'desc')
.startAfter(state.lastDoc).limit(5))
}),
But I get the same 5 results as above from firestore. What am I doing wrong? :(
Internally VueFire and VuexFire use a serializer function that maps each Document returned by RTDB or Firestore into the data objects that are bound to the final component or Vuex store state.
The default serializer is implemented by the function createSnapshot that is part of the vuefire-core library:
/**
* #param {firebase.firestore.DocumentSnapshot} doc
* #return {DocumentData}
*/
export function createSnapshot (doc) {
// defaults everything to false, so no need to set
return Object.defineProperty(doc.data(), 'id', {
value: doc.id
})
}
As you can see it returns only doc.data() (with id added) and discards the doc object. However when implementing Firestore pagination via query.startAfter(doc) we need the original doc object. The good news is that VueFire and VuexFire APIs allow us to replace the serializer with our own that can preserve the doc object like so:
const serialize = (doc: firestore.DocumentSnapshot) => {
const data = doc.data();
Object.defineProperty(data, 'id', { value: doc.id });
Object.defineProperty(data, '_doc', { value: doc });
return data;
}
We can configure our new VuexFire serializer either globally via plugin options or per binding via binding options.
// Globally defined
Vue.use(firestorePlugin, { serialize });
// OR per binding
bindFirebaseRef('todos', db.ref('todos'), { serialize } )
For VuexFire, we can now get access to the first document as state.todos[0]._doc or last document state.todos[state.todos.length-1]._doc and use them to implement pagination queries for collections or "get next" & "get previous" queries that bind single documents (essential when your base query has multi-field sorting).
NOTE: Because _doc and id are non-enumerable properties, they won't appear on component or store objects in Vue DevTools.
From the VueFire documentation on binding data and using it, the $bind method (which I assume your bindFirestoreRef wraps) returns a promise with the result (as well as binding it to this). From that documentation:
this.$bind('documents', documents.where('creator', '==', this.id)).then(documents => {
// documents will point to the same property declared in data:
// this.documents === documents
})
So you should be able to do the same, and then get the document from the array with something like:
bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30)).then(documents => {
this.lastDoc = documents[documents.length - 1];
})