Cloud Functions in Firebase Realtime Database - javascript

i've a database structure like this:
root: {
timer: 1,
data: {
id1: val,
id2: val,
},
cronology: {
id:{
id1: val,
id2: val,
},
id:{
id1: val,
id2: val,
}
}
I'm trying to make a trigger that, when the timer value is modified, it adds to cronology a child that contains the actual values of the path: '/data'.
So i created a function with the onUpdate method on the DatabaseReference '/timer'. The problem is that i don't know how to access to '/data' values in this function
export const cronologyBuilder = functions.database.ref("/timer")
.onUpdate((change, context) => {
const newdata = admin.database.ref('/data');
return admin.database.ref('/cronology').set(newdata);
});
I tried with this code but it tells me that newdata object is not good for set method.
How can i get the values of '/data' path ?

Your code is not actually querying for any data. This line of code just builds a Reference object. It does not contain any data:
const newdata = admin.database.ref('/data');
If you want to query that location, you will need to call once() on it and use the returned promise to wait for the data at that location:
return admin.database.ref('/data').once('value')
.then(snapshot => {
return admin.database.ref('/cronology').set(snapshot.val());
})
I suggest reading the documentation for the Admin SDK database API to learn how to read and write data. You will also need to understand how promises work in order to deal with them properly.

Related

Concatenating arrays in a Alpine.data objects returns proxies

I'm working with a Alpine.data global and I'm looking to concatenate arrays when my loadJobs method fires. I'm not familiar to proxies in JS and I don't really understand why my data property returns a Proxy object instead of a simple array.
Here's the piece of code I'm working with :
jobState = {
low: 5,
high: 10,
isLoaded: false
}
document.addEventListener("alpine:init", () => {
Alpine.data("loadMoreJobs", () => ({
data: [],
loadJobs() {
// Update jobState
jobState.low = jobState.high
jobState.high += 5
const requestParams = {
dataType: 'json',
method: 'GET'
}
fetch(`http://127.0.0.1:3000/jobs/list/${jobState.low}/${jobState.high}`, requestParams)
.then((data) => {
return data.json()
})
.then((data) => {
this.data = [...this.data, data]
})
}
}))
})
data logs out as Proxy because that's how Alpine.js' reactivity works: it wraps all relevant objects in Proxy in order to hook into changes into said objects.
The Proxies should work the same as the underlying data structures, the only difference is Alpine can hook into operations on the proxy (re-assignment, mutation etc.).
As mentioned in the comments thread. One way to unwrap the Proxy and get a copy of the original data structure is to use JSON.parse(JSON.stringify(data)) (or another deep-cloning approach eg. structuredClone which isn't widely available yet).

React set state to nested object value

I need to set state on nested object value that changes dynamically Im not sure how this can be done, this is what Ive tried.
const [userRoles] = useState(null);
const { isLoading, user, error } = useAuth0();
useEffect(() => {
console.log(user);
// const i = Object.values(user).map(value => value.roles);
// ^ this line gives me an react error boundary error
}, [user]);
// This is the provider
<UserProvider
id="1"
email={user?.email}
roles={userRoles}
>
The user object looks like this:
{
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
I need to grab the roles value but its parent, "website.com" changes everytime I call the api so i need to find a way to search for the roles.
I think you need to modify the shape of your object. I find it strange that some keys seem to be fixed, but one seems to be variable. Dynamic keys can be very useful, but this doesn't seem like the right place to use them. I suggest that you change the shape of the user object to something like this:
{
name: "GGG",
site: {
url: "website.com",
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
In your particular use case, fixed keys will save you lots and lots of headaches.
You can search the values for an element with key roles, and if found, return the roles value, otherwise undefined will be returned.
Object.values(user).find(el => el.roles)?.roles;
Note: I totally agree with others that you should seek to normalize your data to not use any dynamically generated property keys.
const user1 = {
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
const user2 = {
name: "GGG",
friends: {},
otherData: {}
}
const roles1 = Object.values(user1).find(el => el.roles)?.roles;
const roles2 = Object.values(user2).find(el => el.roles)?.roles;
console.log(roles1); // ["SuperUser"]
console.log(roles2); // undefined
I would recommend what others have said about not having a dynamic key in your data object.
For updating complex object states I know if you are using React Hooks you can use the spread operator and basically clone the state and update it with an updated version. React hooks: How do I update state on a nested object with useState()?

React - Create objects from API call and store in one big object

I have a program that uses Axios to get data with API calls. I want to store the result as a object in my this.state.matrixDictionary variable. but everytime i make another API call the previous object gets overwritten. I want to create something like this
this.setState({
matrixDictionary: {
[0]: result,
}
})
Then next time i make another api call to get other result i want it to be like this:
this.setState({
matrixDictionary: {
[0]: result,
[1]: result,
}
})
But i dont want to add the [1] manually, i want it to be created depending on how many times i make the API call to store the objects. If i make 5 calls then the object should be now [0],[1],[2],[3],[4] so i can easily keep track of the objects and change their values later.
How is this best achieved?
fetchDataAPI(APIUrl){
this.setState({ isLoading: true });
console.log("Fetching from: " + APIUrl);
return axios.get(APIUrl,{
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
})
.then(result => {
this.setState({isLoading: false});
console.log(result.data);
return result.data;
})
.catch(error => {
this.setState({error, isLoading: false })});
}
UPDATE
I used the fix from Roman Batsenko, Next question is how do I then change a property in that object and put it back in setState.
I guess good practice is to use JS Spread syntax for that like ...state.
It depends on the format of answer from your API but I think it would be not so hard to achieve this with:
axios.get(APIUrl,{
/* ... */
})
.then(result => {
this.setState({
isLoading: false,
matrixDictionary: [...this.state.matrixDictionary, result.data]
});
})
make an array of object in your intial state like
this.state = {
matrixDictionary: []
}
and when you call your api push your response object in array so that will store always in another index and finally you make array of objects.
this.setState({ matrixDictionary: result.data});
it may help you.
Why not save the objects in an array, so you can have them in order:
in the constructor:
this.state = {
matrixDictionary: []
}
in your API call:
this.setState(prevState => ({
values: prevState.matrixDictionary.concat(result.data),
}));
You can access them like this:
this.state.matrixDictionary[0] // your first api call
this.state.matrixDictionary[1] // your second api call
this.state.matrixDictionary[2] // your third api call

Update graphql context in a mutation for the output object of the same mutation

I want to use a single mutation for the application to send user info to server and then get the top level query in the output. (I know this is not a good convention but I want to do this to test if I can improve performance).
So as a result, there will only be one mutation that takes user's info and returns the feed. This mutation updates the information about the user that is fetched in every query as the context of request. The context is used to generate personalized feed. However when I call this mutation, the output returned is calculated using old context. What I need to do is update the context for this same mutation too.
I put down a simplified version of the code to show what's happening:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
someData: {
type: GraphQLList(Post),
resolve: (user, args, context) => getFeed(context) // context in here is the old context.
},
})
})
const someMutation = mutationWithClientMutationId({
name: 'someMutation',
inputFields: {
location: { type: GraphQLString },
},
outputFields: {
user: {
type: UserType,
resolve: (source, args, context) => getUser(context.location),
},
},
mutateAndGetPayload: async (data, context) => {
updateUserInfo(data)
// I have tried updating context like this but it's not working.
context = { location: data.location }
return {
// I even tried putting user here like this:
// user: getUser(data.location)
// However, the resulting query fails when running getFeed(context)
// the context is still the old context
}
},
})
This is just how JavaScript works. You can reassign the value of a function parameter, but that will not change the value the function was passed.
function makeTrue (value) {
value = true
console.log(value) // true
}
var myVariable = false
makeTrue(myVariable)
console.log(myVariable) // false
If the value you pass to the function is an object or an array, you can mutate it and the original value will be mutated as well because objects and arrays in Javascript are passed by reference.
function makeItTrue (value) {
value.it = true
console.log(value.it) // true
}
var myVariable = { it: false }
makeTrue(myVariable)
console.log(myVariable.it) // true
In other words, you need to mutate the context parameter instead of reassigning it.

What is Firebase Firestore 'Reference' data type good for?

I'm just exploring the new Firebase Firestore and it contains a data type called reference. It is not clear to me what this does.
Is it like foreign key?
Can it be used to point to a collection that is located somewhere else?
If reference is an actual reference, can I use it for queries? For example can I have a reference that points directly to the user, instead of storing the userId in a text field? And can I use this user reference for querying?
Adding below what worked for me using references in Firestore.
As the other answers say, it's like a foreign key. The reference attribute doesn't return the data of the reference doc though. For example, I have a list of products, with a userRef reference as one of the attributes on the product. Getting the list of products, gives me the reference of the user that created that product. But it doesn't give me the details of the user in that reference. I've used other back end as a services with pointers before that have a "populate: true" flag that gives the user details back instead of just the reference id of the user, which would be great to have here (hopefully a future improvement).
Below is some example code that I used to set the reference as well as get the collection of products list then get the user details from the user reference id given.
Set a reference on a collection:
let data = {
name: 'productName',
size: 'medium',
userRef: db.doc('users/' + firebase.auth().currentUser.uid)
};
db.collection('products').add(data);
Get a collection (products) and all references on each document (user details):
db.collection('products').get()
.then(res => {
vm.mainListItems = [];
res.forEach(doc => {
let newItem = doc.data();
newItem.id = doc.id;
if (newItem.userRef) {
newItem.userRef.get()
.then(res => {
newItem.userData = res.data()
vm.mainListItems.push(newItem);
})
.catch(err => console.error(err));
} else {
vm.mainListItems.push(newItem);
}
});
})
.catch(err => { console.error(err) });
References are very much like foreign keys.
The currently released SDKs cannot store references to other projects. Within a project, references can point to any other document in any other collection.
You can use references in queries like any other value: for filtering, ordering, and for paging (startAt/startAfter).
Unlike foreign keys in a SQL database, references are not useful for performing joins in a single query. You can use them for dependent lookups (which seem join like), but be careful because each hop will result in another round trip to the server.
For those looking for a Javascript solution to querying by reference - the concept is that, you need to use a 'document reference' object in the query statement
teamDbRef = db.collection('teams').doc('CnbasS9cZQ2SfvGY2r3b'); /* CnbasS9cZQ2SfvGY2r3b being the collection ID */
//
//
db.collection("squad").where('team', '==', teamDbRef).get().then((querySnapshot) => {
//
}).catch(function(error) {
//
});
(Kudos to the answer here: https://stackoverflow.com/a/53141199/1487867)
According to the #AskFirebase https://youtu.be/Elg2zDVIcLo?t=276
the primary use-case for now is a link in Firebase console UI
A lot of answers mentioned it is just a reference to another document but does not return data for that reference but we can use it to fetch data separately.
Here is an example of how you could use it in the firebase JavaScript SDK 9, modular version.
let's assume your firestore have a collection called products and it contains the following document.
{
name: 'productName',
size: 'medium',
userRef: 'user/dfjalskerijfs'
}
here users have a reference to a document in the users collection. we can use the following code segment to get the product and then retrieve the user from the reference.
import { collection, getDocs, getDoc, query, where } from "firebase/firestore";
import { db } from "./main"; // firestore db object
let productsWithUser = []
const querySnaphot = await getDocs(collection(db, 'products'));
querySnapshot.forEach(async (doc) => {
let newItem = {id: doc.id, ...doc.data()};
if(newItem.userRef) {
let userData = await getDoc(newItem.userRef);
if(userData.exists()) {
newItem.userData = {userID: userData.id, ...userData.data()}
}
productwithUser.push(newItem);
} else {
productwithUser.push(newItem);
}
});
here collection, getDocs, getDoc, query, where are firestore related modules we can use to get data whenever necessary. we use user reference returned from the products document directly to fetch the user document for that reference using the following code,
let userData = await getDoc(newItem.userRef);
to read more on how to use modular ver SDK refer to official documentation to learn more.
If you don't use Reference data type, you need to update every document.
For example, you have 2 collections "categories" and "products" and you stored the category name "Fruits" in categories to every document of "Apple" and "Lemon" in products as shown below. But, if you update the category name "Fruits" in categories, you also need to update the category name "Fruits" in every document of "Apple" and "Lemon" in products:
collection | document | field
categories > 67f60ad3 > name: "Fruits"
collection | document | field
products > 32d410a7 > name: "Apple", category: "Fruits"
58d16c57 > name: "Lemon", category: "Fruits"
But, if you store the reference of "Fruits" in categories to every document of "Apple" and "Lemon" in products, you don't need to update every document of "Apple" and "Lemon" when you update the category name "Fruits" in categories:
collection | document | field
products > 32d410a7 > name: "Apple", category: categories/67f60ad3
58d16c57 > name: "Lemon", category: categories/67f60ad3
This is the goodness of Reference data type.
Belatedly, there are two advantages from this blog:
if I expect that I'll want to order restaurant reviews by rating, or publish date, or most upvotes, I can do that within a reviews subcollection without needing a composite index. In the larger top level collection, I'd need to create a separate composite index for each one of those, and I also have a limit of 200 composite indexes.
I wouldn't have 200 composite indices but there are some constraints.
Also, from a security rules standpoint, it's fairly common to restrict child documents based on some data that exists in their parent, and that's significantly easier to do when you have data set up in subcollections.
One example would be restricting to insert a child collection if the user doesn't have the privilege in the parent's field.
2022 UPDATE
let coursesArray = [];
const coursesCollection = async () => {
const queryCourse = query(
collection(db, "course"),
where("status", "==", "active")
)
onSnapshot(queryCourse, (querySnapshot) => {
querySnapshot.forEach(async (courseDoc) => {
if (courseDoc.data().userId) {
const userRef = courseDoc.data().userId;
getDoc(userRef)
.then((res) => {
console.log(res.data());
})
}
coursesArray.push(courseDoc.data());
});
setCourses(coursesArray);
});
}
UPDATE 12/18/22 - I put this in a package.
Original Blog Post
What this package does is use RXJS to loop through each field in a document. If that document type is a Reference type, then it grabs that foreign document type. The collection version grabs the foreign key value for each reference field in all documents in your collection. You can also input the fields manually that you wish to parse to speed up the searching (see my post). This is definitely not as efficient as doing manual aggregations with Firebase Functions, as you will get charged for lots of reads for each document you read, but it could come in handy for people that want a quick way to join data on the frontend.
This could also come in handy if you cache data and really only need to do this once.
J
install
npm i j-firebase
import
import { expandRef, expandRefs } from 'j-firebase';
https://github.com/jdgamble555/j-firebase
Original Post
Automatic JOINS:
DOC
expandRef<T>(obs: Observable<T>, fields: any[] = []): Observable<T> {
return obs.pipe(
switchMap((doc: any) => doc ? combineLatest(
(fields.length === 0 ? Object.keys(doc).filter(
(k: any) => {
const p = doc[k] instanceof DocumentReference;
if (p) fields.push(k);
return p;
}
) : fields).map((f: any) => docData<any>(doc[f]))
).pipe(
map((r: any) => fields.reduce(
(prev: any, curr: any) =>
({ ...prev, [curr]: r.shift() })
, doc)
)
) : of(doc))
);
}
COLLECTION
expandRefs<T>(
obs: Observable<T[]>,
fields: any[] = []
): Observable<T[]> {
return obs.pipe(
switchMap((col: any[]) =>
col.length !== 0 ? combineLatest(col.map((doc: any) =>
(fields.length === 0 ? Object.keys(doc).filter(
(k: any) => {
const p = doc[k] instanceof DocumentReference;
if (p) fields.push(k);
return p;
}
) : fields).map((f: any) => docData<any>(doc[f]))
).reduce((acc: any, val: any) => [].concat(acc, val)))
.pipe(
map((h: any) =>
col.map((doc2: any) =>
fields.reduce(
(prev: any, curr: any) =>
({ ...prev, [curr]: h.shift() })
, doc2
)
)
)
) : of(col)
)
);
}
Simply put this function around your observable and it will automatically expand all reference data types providing automatic joins.
Usage
this.posts = expandRefs(
collectionData(
query(
collection(this.afs, 'posts'),
where('published', '==', true),
orderBy(fieldSort)
), { idField: 'id' }
)
);
Note: You can also now input the fields you want to expand as a second argument in an array.
['imageDoc', 'authorDoc']
This will increase the speed!
Add .pipe(take(1)).toPromise(); at the end for a promise version!
See here for more info. Works in Firebase 8 or 9!
Simple!
J

Categories