How to use arrayUnion with AngularFirestore? - javascript

I have a basic database that essentially stores an array of product id's underneath a user. The user can select products to add to the array so it makes sense to use 'arrayUnion' so I avoid reading and re-writing the array constantly, however, I keep getting the error *"Property 'firestore' does not exist on type 'FirebaseNamespace'."
I've followed the documentation found at: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array but I fear I'm still using it incorrectly.
My code for updating the array is:
addToPad(notepadName: string){
const updateRef = this.db.collection('users').doc(this.activeUserID).collection('notepads').doc(notepadName);
updateRef.update({
products: firebase.firestore.FieldValue.arrayUnion(this.productId)
});
}

First you need to import firestore:
import { firestore } from 'firebase/app';
Then you will be able to use arrayUnion:
addToPad(notepadName: string){
const updateRef = this.db.collection('users').doc(this.activeUserID).collection('notepads').doc(notepadName);
updateRef.update({
products: firestore.FieldValue.arrayUnion(this.productId)
});
}

import { arrayUnion } from '#angular/fire/firestore'
const path = `ai/${videoId}/panel-operation/${id}`
const myDoc: AngularFirestoreDocument<any> = this.afs.doc<any>(path)
const promise: Promise<void> = myDoc.update({ auxPanelSelections: arrayUnion({auxPanel: 'clip', operation: 'replace'}) }).catch((err: any) => {
console.error(`oopsie - ${err.message}`)
return null
})
auxPanelSelections is an array within the myDoc document
Note that the above code also works perfectly fine with arrayRemove
I cannot find the #angular/fire docs for arrayUnion but the generic docs are here

Related

How to clear and sanitize complex objects or any object in javascript

I'm trying to log every possible error to a firestore document, to achieve this, I have a function which receives the error object, and saves it to the firestore document... the thing is... that this error object can be anything, it can be a single string and not an object, or it could be a complex object, appearently with functions aswell, but firestore won't accept some data types, for example if the object has an undefined property firestore will not accept it...
So I'm trying to find a way to sanitize or clean an object from which I don't know what properties could have.
Right now i'm using this:
import {db} from 'src/firebase/config'
import { addDoc, collection } from 'firebase/firestore';
import {Platform} from 'quasar'
export const saveErrorLog = async (hotel, uid, ref, error) => {
const guestRef = collection(db, 'error_logs');
const cleanErrorObject = Object.entries(error).reduce((a,[k,v]) => (v ? (a[k]=v, a) : a), {})
const logData = {
platform: Platform.is || null,
ref: ref || null,
error: { ...cleanErrorObject },
hotel: hotel || null,
uid: uid || null
}
await addDoc( guestRef, logData )
}
This works most of the time, but sometimes firebase returns the same error of unsupported data types... I think it is because some error objects have functions aswell, but I'm not sure about it.
Thanks in advance.

Get collection reference in redux-saga-firebase

I'm unable to define a collection reference using redux-saga-firebase. I'm trying to define it like this:
const query = rsf.firestore.collection('players').where('up', '>', lastUpdated);
but I am getting
TypeError: _firebase_firebase__WEBPACK_IMPORTED_MODULE_6__.rsf.firestore.collection is not a function
I have tried also tried using .reference() as described here to no avail. I'm attempting to limit the number of documents synced from a collection per this suggestion. How can I get a collection reference for use in this function?
import { rsf } from '../../firebase/firebase';
export function* getPlayersFromDb() {
const players = yield select(getPlayers);
const lastUpdated = Math.max(...players.map(x => x.up));
const query = rsf.firestore.collection('players').where('up', '>', lastUpdated);
yield fork(
rsf.firestore.syncCollection,
query,
{ successActionCreator: response => ({
type: t.GET_PLAYERS_FROM_DB_SUCCESS,
payload: [...response],
}),
failureActionCreator: () => ({
type: t.GET_PLAYERS_FROM_DB_FAILURE,
payload: [],
}),
transform: response => messageTransformer(response),
}
);
}
The problem with your code is that you are mixing the official firestore SDK libraries with the redux-saga-firestore ones. If you want to get a collectionReference you have to do it using the official SDK, since the redux-saga-firestore libraries do not offer a method that returns that output.
So, in order to get the collectionReference you would need to import the Firestore SDK and use almost the same code you already have on the getPlayersFromDb function, and it would look like this:
const firebase = require("firebase");
require("firebase/firestore");
var db = firebase.firestore();
...
export function* getPlayersFromDb() {
...
const query = db.collection('players').where('up', '>', lastUpdated);
...
}
NOTE: As you can see on this redux-saga-firestore documentation, you could be using the getCollection method for that, but since you have a where clause, you would need to use a collectionReference to filter it anyway, so I am afraid there is no real solution for this with redux-saga-firestore.
Hope this helps

Having trouble converting schema from Normalizr v2 to v3

I just updated to normalizr version 3.1.x so I can utilize the denormalization. Though they've significantly changed their API. I'm having trouble transferring my schemas over.
import { normalize, Schema, arrayOf, valuesOf } from 'normalizr';
const usersSchema = new Schema('users')
const photosSchema = new Schema('photos')
const phonesSchema = new Schema('phones')
photosSchema.define({
users: arrayOf(usersSchema)
})
phonesSchema.define({
users: arrayOf(usersSchema)
})
usersSchema.define({
photos: valuesOf(photosSchema),
phones: valuesOf(phonesSchema)
})
That was my existing schema for users. I was also using the redux-normalizr middleware inside my redux action, so I connected the schema to my action like this:
import { usersSchema } from '../normalizrSchemas/usersSchemas.js'
import { arrayOf } from 'normalizr'
export function getUsers(data) {
return {
type: 'GET_USERS',
payload: data,
meta: {
schema : arrayOf(usersSchema)
}
}
}
This was my first attempt to convert the schema over. It doesn't seem you can call schema.Array the same way you could using arrayOf, so I thought I needed to move the array call into the schema.
import { schema } from 'normalizr';
const photos = new schema.Entity('photos')
const phones = new schema.Entity('phones')
const user = new schema.Entity('user', {
photos: [photos],
phones: [phones]
})
const users= new schema.Array('users', user)
export { users }
the action is the same, but i've removed wrapping the schema in arrayOf. All of the users data is just getting dumped into results without any normalization. The data is a list of user object, and each object contains an id, which normalizr should pick up. I'm struggling to figure out how to get normalizr the identify that it's an array of object I think.
schema.Array does not accept a key string name (docs). The first argument should be the schema definition. So instead of
const users= new schema.Array('users', user)
You should use:
const users = new schema.Array(user)
Or, you could just use the shorthand for an array of a single entity type:
const users = [ user ];

Immutable.js deleteIn not working

I have been trying to solve this problem, but there is probably something of Immutable.js that I don't catch. I hope somebody can help me to understand.
I have a test like this:
import {List, Map} from 'immutable';
import {expect} from 'chai';
import {setInitial,
addAtListOfMembers,
removeAtListOfMembers
} from '../src/core';
describe('removeAtListOfMembers', () => {
it('remove a member to the list of members', () => {
const state = Map({
removing: 3,
infos : Map(),
members: Map({
1:Map({
userName:'René',
date:'12/02/2016'
}),
2:Map({
userName:'Jean',
date:'10/03/2016'
}),
3:Map({
userName:'Elene',
date:'05/01/2016'
})
})
});
const nextState = removeAtListOfMembers(state);
expect(nextState).to.equal(Map({
infos : Map(),
members: Map({
1:Map({
userName:'René',
date:'12/02/2016'
}),
2:Map({
userName:'Jean',
date:'10/03/2016'
})
})
}));
});
});
});
...witch tests this funtion:
export function removeAtListOfMembers(state) {
const members = state.get('members');
const removing = state.get('removing');
return state
.deleteIn(['members'], removing)
.remove('removing');
}
but it doesn't work. I have tryed everything.... changing the line to make it work, but I don't get the item number 3 deleted.
What's wrong? Somebody to help me?
This should work:
export function removeAtListOfMembers(state) {
const members = state.get('members');
const removing = state.get('removing');
return state
.deleteIn(['members', String(removing) ])
.remove('removing');
}
Your code has two issues:
deleteIn takes a single keyPath argument, which in your case is [ 'members' ]. The second argument (removing) is ignored, so the result is that the entire members map is deleted; instead, removing should become part of the key path.
removing is a Number, but because you're creating a Map from a JS object, its keys will be String's (this is mentioned in the documentation as well):
Keep in mind, when using JS objects to construct Immutable Maps, that JavaScript Object properties are always strings
So you need to convert removing to a String when passing it to deleteIn.

Updating nested objects firebase

From the Firebase note:
Given a single key path like alanisawesome, updateChildren() only updates data at the first child level, and any data passed in beyond the first child level is a treated as a setValue() operation. Multi-path behavior allows longer paths (like alanisawesome/nickname) to be used without overwriting data. This is why the first example differs from the second example.
I am trying to use a single function createOrUpdateData(object) in my code. In case of update, it updates first level children properly, but if I have nested object passed, then it deletes all other properties of that nested object.
Here's the code:
function saveUserDetails(email,object){
var hashedEmail = Utilities.getHashCode(email);
var userRef = ref.child(hashedEmail);
return $q(function(resolve,reject){
return userRef.update(object, function(error){
if(error){
reject(error);
}else{
resolve("Updated successfully!");
}
});
});
}
So if I pass:
{
name: 'Rohan Dalvi',
externalLinks: {
website: 'mywebsite'
}
}
Then it will delete other properties inside externalLinks object. Is there a cleaner and simpler way of avoiding this?
In short, how do I make sure nested objects are only updated and that data is not deleted.
You can use multi-path updates.
var userRef = ref.child(hashedEmail);
var updateObject = {
name: 'Rohan Dalvi',
"externalLinks/website": 'mywebsite'
};
userRef.update(updateObject);
By using the "externalLinks/website" syntax in the object literal it will treat the nested path as an update and not a set for the nested object. This keeps nested data from being deleted.
This question provides a more recent solution that works with cloud firestore.
Rather than using "/", one may use "." instead:
var userRef = ref.child(hashedEmail);
var updateObject = {
name: 'Rohan Dalvi',
"externalLinks.website": 'mywebsite'
};
userRef.update(updateObject);
To update nested object/map/dictionary in firebase database, you can use Firestore.Encoder to class/struct that is Codable.
Here is a Swift code example:
Models:
import FirebaseFirestore
import FirebaseFirestoreSwift
// UserDetails Model
class UserDetailsModel: Codable {
let name: String,
externalLinks: ExternalLinkModel
}
// UserDetails Model
class ExternalLinkModel: Codable {
let website: String
}
Calling Firebase:
import FirebaseFirestore
import FirebaseFirestoreSwift
let firestoreEncoder = Firestore.Encoder()
let fields: [String: Any] = [
// using firestore encoder to convert object to firebase map
"externalLinks": try! firestoreEncoder.encode(externalLinkModel)
]
db.collection(path)
.document(userId)
.updateData(fields, completion: { error in
...
})

Categories

Resources