How to save resulted values from firestore into a variable and use it on various components and states in Vue - firebase

I'm looking for a way to save the results received from a firestore collection and use it on a variable and reuse the save it to data() and reuse it.
I'm unsure how to save the results from a async function into a variable.
Please help me with this guys
Here is my code.
import firebase from "firebase";
export default{
data() {
return {
uid: firebase.auth().currentUser.uid,
teamLead: ""
};
},
created() {
db.collection("users").onSnapshot(users => {
users.docChanges().forEach(user => {
if (this.uid == user.doc.id) {
this.teamLead = user.doc.data().teamLead
}
});
});
console.log(this.teamLead)
In this code I would like to have the value of teamLead saved into the data() function. How do I do that?

It's a crazy idea to read all user from db. Instead of you can retrieve user by id.
Don't use "this" inside callbacks or lambda.
You have to understand order of execution of async programs. See my comments.
[]
export default{
data() {
return {
teamLead: ""
};
},
created() {
let self = this
db.collection("users").doc(firebase.auth().currentUser.uid).get().then(function(doc) {
if (doc.exists) {
self.teamLead = doc.data().teamLead
console.log(self.teamLead)//<-- this log will executed after firestore return any data
}
});
console.log(this.teamLead)//<-- this log will executed before firestore return any data

Related

Why my Redux App return that [ Immer ] error?

I don't know. Why even I added my push function on my object to return my new result, The app is printing error on my console.log.
slice.js
import { createSlice } from '#reduxjs/toolkit';
import { pushProduct } from '../commons/push';
export const slice = createSlice({
name: 'initial',
initialState : {
product: [],
},
reducers: {
ADDS(state, actions) {
return {
...state,
product: pushProduct(state.product, actions.payload),
console1: console.log('State: ', state.product),
console2: console.log('Actions: ', actions.payload),
}
}
}
});
export const { ADDS } = slice.actions;
export default slice.reducer;
push.js
// Push new prpduct to the cart
export const pushProduct = (initial, productSelect) => { return initial.push(productSelect) };
console.log error
errors.ts:49 Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.
Thank You
Per the error message: Immer lets you update the state in two ways. One is "mutating" the existing state, and the other is returning a new value. But, you can only do one of those at a time.
You're trying to do both. You have return {...state}, but you also have pushProduct() which sounds like it's mutating.
The best answer here is to not try to do return {...state} at all, and just "mutate" the existing state.
See https://redux-toolkit.js.org/usage/immer-reducers#mutating-and-returning-state for more details.

How to test nested firestore batch functions in redux saga using jest?

In a react project I have a redux-saga file which I create and save new items on firebase backend(firestore).
In that saga function, I am getting a new write batch object first, then I update the firestore document, and finally I commit the batch.
Saga Worker
import { call, put } from 'redux-saga/effects'
import { db } from './firebase' // db: firebase.firestore()
export function* mySaga(item) {
try {
// init firestore batch.
const batch = yield call(db, db.batch)
// Set firestore document and save new item.
const itemRef = yield call ([db, db.doc], `/items/${item.id}`)
yield call([batch, batch.set], itemRef , item)
// Commit the batch.
yield call([batch, batch.commit])
yield put({type: 'success'})
} catch (err) {
yield put({type: 'error', payload: err})
}
}
Saga Worker's Test
import * as sagas from './mySaga'
describe('mySaga', () => {
const spyOnDoc = jest.spyOn(db, 'doc')
it('handles item creation', async () => {
const dispatched = []
await runSaga(
{ dispatch: action => dispatched.push(action) },
sagas.mySaga,
).toPromise()
expect(spyOnDoc).toHaveBeenCalledTimes(1)
// !!! Here I need to check for nested set and commit functions of the batch object created in saga.
})
})
How can I test the batch function's nested "set" and "commit" functions to check if they are called x times and called with proper inputs?
Any help would be appreciated.
After several attemps I figured out a way to accomplishing this kind of tests. In case if someone needs this solution, here it is.
db.batch() method creates a firebase.firestore.WriteBatch object. And this object has commit, set, update and delete methods. More details can be found here.
Final Saga Worker's Test
import * as sagas from './mySaga'
import { db } from './firebase' // db: firebase.firestore()
describe('mySaga', () => {
const spyOnDoc = jest.spyOn(db, 'doc')
// We are mocking the methods of this predefined object.
firestore.WriteBatch.set = jest.fn()
firestore.WriteBatch.commit = jest.fn()
// Then we implement those created mocks into the batch's mock implementation.
const spyOnBatch = jest.spyOn(db, 'batch').mockImplementation(() => ({
set: firestore.WriteBatch.set,
commit: firestore.WriteBatch.commit,
}))
it('handles item creation', async () => {
const dispatched = []
await runSaga(
{ dispatch: action => dispatched.push(action) },
sagas.mySaga,
{id: 123} // Item
).toPromise()
expect(spyOnDoc).toHaveBeenCalledTimes(1)
// Finally, we can test those nested object functions as below.
expect(firestore.WriteBatch.commit).toHaveBeenCalledTimes(1)
expect(firestore.WriteBatch.set).toHaveBeenCalledTimes(1)
expect(firestore.WriteBatch.set).toHaveBeenCalledWith(db.doc('/items/123'), {id: 123})
})
})

Firebase Firestore returns a promise in Vue

I'm trying to use some data from from Firestore. before it used to work, now in Vuetify I keep getting 'PENDING' if I try to access the $data.users
export default {
data() {
return {
users: [],
};
},
created() {
db.collection('users').get().then((snapshot) => {
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
this.users = user;
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
});
},
methods: {
imageUrl(user) {
console.log(user.documents.selfie.url); // Here the log return "Pending";
},
Inside the template I run a v-for (user, index) in users :key='index'
ERROR:
Uncaught (in promise) TypeError: Cannot read property 'selfie' of undefined
It's difficult to be 100% sure without reproducing your problem, but I think the problem comes from the fact that the Promise returned by the asynchronous get() method is not yet fulfilled when you call the imageUrl() method. This is why you get the pending value.
One possibility to solve that is to check as follows:
methods: {
imageUrl(user) {
if (user) {
console.log(user.documents.selfie.url);
} else {
//...
}
},
Also, is seems you want to populate the users Array with the docs from the users collection. You should do as follows:
created() {
db.collection('users').get().then((snapshot) => {
let usersArray = [];
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
usersArray.push(user);
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
this.users = usersArray;
});
},
With your current code you assign the last user in the loop, not the list of users.

Get data from Firebase in Vue.js component

I'm starting my first serious app with Vue.js and I have an issue gathering data from Firabase. The idea here is simply to get data linked to an user ID. My first though was to store that in a computed value, like so
export default {
...
computed: {
userInfo: function() {
const firestore = firebase.firestore();
const docPath = firestore.doc('/users/' + firebase.auth().currentUser.uid);
docPath.get().then((doc) => {
if (doc && doc.exists) {
return doc.data();
}
});
}
}
}
But, when I try to access this variable, it's undifined.
My guess is that the value is computed before the asynchronous call has ended. But I can't see how to get around it.
Indeed you have to take into account the asynchronous character of the get() method. One classical way is to query the database in the created hook, as follows:
export default {
data() {
return {
userInfo: null,
};
},
....
created() {
const firestore = firebase.firestore();
const docPath = firestore.doc('/users/' + firebase.auth().currentUser.uid);
docPath.get().then((doc) => {
if (doc && doc.exists) {
this.userInfo = doc.data();
}
});
}
}

How to listen for specific Firestore document creation event?

I am implementing a command/response pattern where the user writes to a command collection by calling add with a payload under his own userId, and then gets the data from a similar response path.
However the code below doesn't work, because onSnapshot can not listen for a document that hasn't yet been created (document command.id in the /responses/{userId}/register collection). This would be easy to solve with an onCreate handler, which exists for cloud functions but not for the JS firebase client API it seems.
This is using redux-firestore and some of my app helper functions, but you'll get the idea. The command and response document structures use { payload, error} similar to FSA
Cloud Function
export const register = functions.firestore
.document("commands/{userId}/register/{commandId}")
.onCreate(async event => {
const payload = event.data.get("payload");
const { userId, commandId } = event.params;
const response = db.document(`responses/${userId}/register/${commandId}`)
// possibly something bad will happen
try {
// do something with payload...
return response.set({
payload: "ok" // or pass relevant response data
})
} catch(err) {
return response.set({
error: true
payload: error
})
}
});
Client
export async function register(fs: any, userId: string) {
try {
// issue a new command
const command = await fs.add(
{ collection: `commands/${userId}/register` },
{ payload: fs.firestore.FieldValue.serverTimestamp() }
);
// wait for the response to be created
fs.onSnapshot(
{ collection: `responses/${userId}/register`, doc: command.id },
function onNext(doc) {
const {error, payload} = doc.data()
if (error) {
return notify.error({ title: 'Failed to register', message: payload.message });
}
notify.json(payload);
},
function onError(err) {
notify.error(err);
}
);
} catch (err) {
notify.error(err);
}
}
Is there no such thing as onCreate for web clients?
The only scalable solution I can think of is to store the response data as a child in the command document, but I think it is not as nice, because I suspect you can not make the permissions as strict then.
I would like the user only to be able to write to the command, and only read from the response paths. If I place the response as a child of command, this would not be possible I think?
I'm wondering if I'm not overlooking some API...

Resources