Firebase Multi path atomic update with child values? - javascript

I am succesfully updating my user's profile picture on their profile and on all of their reviews posted with this function:
export const storeUserProfileImage = (url) => {
const { currentUser } = firebase.auth();
firebase.database().ref(`/users/${currentUser.uid}/profilePic`)
.update({ url });
firebase.database().ref('reviews')
.orderByChild('username')
.equalTo('User3')
.once('value', (snapshot) => {
snapshot.forEach((child) => {
child.ref.update({ profilePic: url });
});
});
};
I am aware that I should be using an atomic update to do this so the data updates at the same time (in case a user leaves the app or something else goes wrong). I am confused on how I can accomplish this when querying over child values.
Any help or guidance would be greatly appreciated!

Declare a variable to store all the updates. Add the updates as you read them on your listener's loop. When the loop is finished, run the atomic update.
export const storeUserProfileImage = (url) => {
const { currentUser } = firebase.auth();
firebase.database().ref('reviews')
.orderByChild('username')
.equalTo('User3')
.once('value', (snapshot) => {
var updates = {};
updates[`/users/${currentUser.uid}/profilePic`] = url;
snapshot.forEach((child) => {
updates[`/reviews/${child.key}/profilePic`] = url;
});
firebase.database().ref().update(updates);
});
};

Related

How to save key of each item in the firebase

I'm using firebase in my react native project.I am saving items and then getting those items in my app. But now I want to edit and delete items based on the key generated by the firebase for each item you can see in the ss.
When I get the data from firebase I'm getting on the items but not the key for each item.So that's why I'm unable to edit or delete items
Kindly help me out on how to do this.
enter image description here
here is my code of add item in the firebase.
export const addMeasurement = params => {
return async dispatch => {
dispatch(measurementLoading());
try {
firebaseService
.database()
.ref('/Measurements')
.push(params)
.then(res => {
measurementAdded(res);
});
} catch (err) {
dispatch(measurementFailed(err));
}
};
};
Here is the code to get items from firebase which was stored early
export const getMeasurements = () => {
return async dispatch => {
dispatch(measurementLoading());
try {
const ref = firebaseService.database().ref('/Measurements');
ref.on('value', snapshot => {
const values = snapshot.val();
if (values !== null) {
const newFreshArr = Object.values(values);
dispatch(measurementSuccess(newFreshArr));
}
});
} catch (err) {
dispatch(measurementFailed(err));
}
};
};
You can get the path of a snapshot like so:
let path = snapshot.ref.path.toString();.
You can then make changes to your Firebase database using that path.
Here’s the documentation on that: https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#ref

Test firestore trigger locally

I am writing a test which tests a firebase trigger. The problem, however, is that I cannot make it work.
I want to use the local firestore emulator and Jest in order to simulate a change in the firestore and see if the trigger does what it needs to do.
I require the cloud function in my test and I initialize my app
Setup.js:
const firebase = require('#firebase/testing');
const PROJECT_ID = 'project';
let admin;
let db;
const setupAdmin = async () => {
admin = firebase.initializeAdminApp({
projectId: PROJECT_ID
});
db = admin.firestore();
};
const getAdmin = () => {
return admin;
};
const getDb = () => {
return db;
};
module.exports.setupAdmin = setupAdmin;
module.exports.getAdmin = getAdmin;
module.exports.getDb = getDb;
Test.js
describe('Billing', () => {
let dbRef;
beforeAll(async () => {
const {db, admin} = require('../../../functions/helpers/setup');
dbRef = db;
});
afterAll(async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
console.log(`View rule coverage information at ${COVERAGE_URL}\n`);
});
it('test', async () => {
const mockData = {
'Users/user1': {
uid: 'user1'
},
['Users/user1/Taxes/' + new Date().getFullYear().toString()]: {
totalExpenseEuro: 0
}
};
for (const key in mockData) {
const ref = dbRef.doc(key);
await ref.set(mockData[key]);
}
// Create mockup data
await dbRef.collection('Users').doc('user1').collection('Expenses').doc('expense1').set({
amountEuroInclVAT: 100
});
// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({amountEuroInclVAT: 0}, 'Users/user1/Expenses/expense1');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot(
{amountEuroInclVAT: 100},
'Users/user1/Expenses/expense1'
);
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(calculateTaxesOnExpenseUpdate);
wrapped(change, {
params: {
uid: 'test1'
}
});
});
});
Now the main problem comes when I try to access this db object in my trigger
const calculateTaxesOnExpenseUpdate = functions.firestore
.document('Users/{uid}/Expenses/{expenseId}')
.onWrite(async (change, context) => {
const {getDb} = require('../helpers/setup'); // This setup is the same as above
let db = getDb();
...
For some reason when I perform an action like (await db.collection('Users').get()).get('totalExpenseEuro'), Jest stops executing my code. When I set a debugger right after that line, it never gets printed. That piece of code crashes, and I have no idea why. I think the DB instance if not properly configured in my cloud trigger function.
Question: What is a good way of sharing the DB instance (admin.firestore()) between the test and the cloud trigger functions?

(reactjs) how to set a promise or set the current state into local storage

I'm trying to build a very simple app for searching articles and using the localstorage to display more info about each article but when I set to save into the session storage for some reason is saving the initial or previous state, I assume because I need to set async for this but I just can't figure how
This is how I have it right now, findArticleQuery() is called on the handleSubmit
useEffect(
() =>{
findArticlesQuery();
} ,[]
)
const findArticlesQuery = (query) => { //search function
axios.get(`url`)
.then(res => {
[res.data].map((val) =>(
setState(val)
))
}).catch(error => {
console.log(error.response)
});
}
const handleSubmit = (e) => {
e.preventDefault();
findArticlesQuery(e.target.search.value)
sessionStorage.setItem('localData', JSON.stringify(state)) //<--here I get the value of the previous state
e.target.search.value = '';
}
I need to use the session storage because I will have a detailed article component page.
Thank you guys!
You can get search result from findArticlesQuery() like below.
...
const findArticlesQuery = (query) => { //search function
return axios.get(`url`)
.then(res => {
setState(res.data)
return res.data
})
}
const handleSubmit = (e) => {
e.preventDefault();
findArticlesQuery(e.target.search.value)
.then(val => sessionStorage.setItem('localData', JSON.stringify(val)))
e.target.search.value = '';
}
For saving state in the localStorage with React Hooks, something I've been using and that is extremely convenient is useLocalStorage.
Disclaimer : I am not the creator of this lib.

Firebase real time database - subscription .on() does not trigger on client

I have a collection of notes in my Firebase realtime database.
My code is set to subscribe to modifications in the /notes path of the database.
When updating a note in the Firebase web console my client gets the updates, but when another client updates the note via ref.update() the data is not pushed to my client.
When updating in the Firebase web console, I am performing the update from one browser and watching the update in another browser.
How can this be? See code below.
export const startSubscribeNotes = () => {
return (dispatch, getState) => {
const uid = getState().auth.uid
const dbPath = 'notes'
database.ref(`${dbPath}`)
.orderByChild(`access/members/${uid}`)
.equalTo(true)
.on('value', (snapshot) => {
console.log('notes .on()')
let updatednotes = []
console.log('Existing notes', existingnotes)
snapshot.forEach( (data) => {
const note = {
id: data.key,
...data.val()
}
updatednotes.push(note)
})
dispatch(setnotes(notes))
})
}
}
What I have noticed is that if I change my access rules in the Firebase database I am able to get the updates, but can no longer restrict read access.
"notes": {
//entry-level access
".read": "
auth.uid !== null //&& query.equalTo === auth.uid
",
I can then loop all notes and collect the ones I should only be able to read.
const uid = getState().auth.uid
database.ref('notes')
.on('value', (snapshot) => {
const notes = []
snapshot.forEach((data) => {
const noteData = data.val()
if(noteData.access){
const authorArray = (noteData.access.author) ? [noteData.access.author] : []
const members = (noteData.access.members) ? noteData.access.members : {}
const membersArray = Object.keys(members)
const allUsers = authorArray.concat(membersArray)
const accessGranted = allUsers.includes(uid)
if(accessGranted){
const note = {
id: data.key,
...data.val()
}
notes.push(note)
}
}
})
if(notes.length > 0){ dispatch(setNotes(notes)) } //Dispatch if not empty
})
How can I apply the access rules and still get the updates pushed to my client?
I have checked that all clients can write to the database at /notes
Any help much appreciated!

How to properly map data from multiple nested queries into desired object. React/Firebase

I am trying to map out my schema from Firebase Firestore. I used Flamelink as a headless CMS to make things easier, however I am facing a limitation.
The image storage location is separate from the data that i'm pulling in from my schema. To remedy this I created some nested Queries to find the correct path of the storage location and generate the URL for the image. I need that image URL to be placed back into the respected object somewhere.
Currently it's just adding it to the overall array not the objects already within it. The first console log is the file reference in the correct order, the second console log is the correct URL's I need into the original object. However, these seem to not be in the correct order. I am fairly new to react and ES6 so I could be over complicating things. Essentially I just want the output of the second Console.log into my ThumbnailURL position for each object in the respective place.
I have tried to just create a new Array of objects and map them in my front end, however that causes issues because they are not in the same object.
const storage = firebase.storage().ref('flamelink/media/sized/240/')
class ContentUpdates extends React.Component {
constructor(){
super();
this.ref = firebase.firestore().collection('fl_content').where('_fl_meta_.schema', '==', 'collateral');
this.unsubscribe = null;
this.state = {
collateral: [],
}
}
onCollectionUpdate = (querySnapshot) => {
const collateral = [];
querySnapshot.forEach((doc) => {
const { name, image, thumbnail, thumbnailURL, img} = doc.data();
collateral.push({
key: doc.id,
doc, // DocumentSnapshot
name,
image,
img,
thumbnail: image[0].id,
thumbnailURL: firebase.firestore().collection(`fl_files`).where(`id`, '==', `${image[0].id}`).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const { file } = doc.data();
console.log('this is in the correct order', `${file}`);
storage.child(`${file}`).getDownloadURL().then((url) => {
const fileurl = url
console.log('this is the value I need mapped to thumbnailURL', fileurl)
collateral.push({thumbnailURL: fileurl})
})
})
})
});
});
this.setState({
collateral
});
}
componentDidMount() {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
I want each objected in collateral to go through the necessary queries to return the fileurl back to it's respected collateral object.
UPDATE: I cleaned up the function a bit. Issue now is that it's going through two loops and not returning the values but only a promise. Very close to solving it however. Will update soon.
onCollectionUpdate = (querySnapshot) => {
const collateral = [];
querySnapshot.forEach((doc) => {
const { name, image, thumbnail, thumbnailURL, img} = doc.data();
const thumbnailImg = this.getThumbnailUrl(image[0].id);
let obj = {
key: doc.id,
doc, // DocumentSnapshot
name,
image,
img,
thumbnail: image[0].id,
thumbnailURL: thumbnailImg
}
collateral.push(obj);
});
this.setState({
collateral
});
}
async getThumbnailUrl(imgRef) {
console.log('your in', imgRef)
let snapshot = await firebase.firestore().collection(`fl_files`).where(`id`, '==', imgRef).get();
console.log(snapshot)
let snapVal = snapshot.forEach( async(doc) => {
const { file } = doc.data();
console.log('this is in the correct order', `${file}`);
let downloadURL = await storage.child(`${file}`).getDownloadURL()
console.log('this is the value I need mapped to thumbnailURL', downloadURL)
return downloadURL;
})
return snapVal;
}
componentDidMount() {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}

Categories

Resources