I'm just getting into Firebase, and really digging it so far, but I'm having a bit of an issue using a Cloud Function to remove an item from an array in a collection. In the app I'm using, users have a document in a users collection that contains user-specific data that doesn't fit within the authentication model. The data looks like this:
Screenshot of user collection structure.
(It's a fitness-related app, hence the array being called 'workouts')
The user has the ability to create a workout, and when they do, after it's created, a cloud function watching for new workouts adds the ID of the newly created workout to the user document, appending it to the workouts array using arrayUnion:
exports.addWorkoutToAuthorsListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onCreate((snap, context) => {
const id = context.params.workoutId
const name = snap.data().title
const uid = snap.data().author
const workout_reference = { id: id, name: name, uid: uid }
const workoutsRef = admin.firestore().collection("users").doc(uid)
return workoutsRef.update({
workouts: admin.firestore.FieldValue.arrayUnion(workout_reference)
})
})
This works fine, but I want the user to be able to delete workouts, and I didn't add a string to the workouts array, but instead an object like this:
{ id: "1214", name: "My workout", uid: "123asd" }
Now, for this exact use-case I could delete the array in my ReactJS app and then do an update, but I'm going to be adding the ability for users to "like" the workouts of other users, which will result in a reference to the workout being added to "workouts" in their personal user document. So if I, as a creator of a workout, delete my workout, I need to be able to delete it from the workouts array of any user who has it. Using arrayRemove didn't work because (I assume) I couldn't pass the object to be removed.
What's the best-practices way to do this? I made this attempt:
exports.removeWorkoutFromAuthorsListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onDelete((snap, context) => {
const id = context.params.workoutId
const uid = snap.data().author
const name = snap.data().name
let newWorkouts = new Array()
admin.firestore().collection("users").doc(uid).collection("workouts").get().then((querySnapshot) => {
querySnapshot.forEach((doc: any) => {
if (doc.id !== id) {
newWorkouts.push(doc.data())
}
});
});
const workoutsRef = admin.firestore().collection("users").doc(uid)
return workoutsRef.update({
workouts: newWorkouts
})
})
But Firebase didn't like it at all, and I'm new enough to the platform that I realize this is most likely due to the result of a knowledge gap, rather than any problem with Firestore or Cloud Functions.
Any help you could provide would be very much appreciated. Cheers!
UPDATE: Got it working, with the following code:
exports.removeWorkoutFromSubscribersListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onDelete((snap, context) => {
const workoutId = context.params.workoutId
const subscribers = snap.data().subscribers
let newWorkouts = new Array()
subscribers.forEach(subscriber => {
let userRef = admin.firestore().collection("users").doc(subscriber)
return userRef.get().then(doc => {
return userRef.update({
workouts: doc.data().workouts.filter(workout => workout.id !== workoutId)
})
})
})
.catch(() => {
console.log("error")
})
})
Related
I have a function, updateHistory:
export const updateHistory = async (id, historyObj) => {
const { data, error } = await supabase
.from("property")
.update({ history: historyObj })
.eq("id", id);
return data;
};
I am calling it like so:
useEffect(() => {
let tempHistory = {
column: column,
field: name,
previousValue: `${defaultValue}`,
updatedValue: `${changedValue}`,
time: new Date().toISOString(),
};
let newHistory = [tempHistory, ...history];
setHistory(newHistory);
updateHistory(propertyId, newHistory);
}, [changedValue]);
The goal is to have a log history with all previous values, with the current value being the 1st element in the array of objects. The issue is, with this code, it is just replacing the data in the history column with only the current value, rather than just adding to it and keeping the previous values.
I'm not sure why my current code is not working.
Thanks in advance.
I figured this out. This is not an issue with Supabase. history was null at some points, which replaced the previous values of it with nothing.
I am trying to get a specific id related data from backend instead of whole database and then comparing the id. i am passing id using state param. I am new to react so it will helpful to explain as simply as you can.
this is my service for getting data
import http from "./http";
const getBoxes = () => {
return http
.get("/box/getAllBox")
.then((result) =>
result.data.content.map((item, index) => ({ ...item, key: index }))
);
};
You can easily do that by using the filter() property
For example
import http from "./http";
const getBoxes = () => {
return http
.get("/box/getAllBox")
.then(result=>{
result.data.content.filter(item=> item.id=="your ID")
})
.then((result) =>
result.data.content.map((item, index) => ({ ...item, key: index }))
);
};
Replace your ID with your custom id
You should create a backend endpoint (if possible) where you can get one box. EG /box/{id}.
This means when you get this endpoint in react you can do result.data.{some property from the returning object} eg result.data.boxName
If you don't have the ability to change the backend, you could get the specific id from the array of boxes by looping through the results and finding where ID of the returned object matches the ID you're looking for - this requires the array of objects being returned to each contain an ID field.
import http from "./http";
const getBoxes = () => {
return http
.get("/box/getAllBox")
.then((result) =>
let itemFromID;
for(let i = 0; i < result.data.length; i++) {
let item = result.data[i];
if(item.id === 1) {
itemFromID = item;
break;
}
}
// now you have the item in itemFromID variable
console.log(itemFromID);
);
};
I am new to the react world. I am trying to write a code in which when an icon is pressed the Image is added into a favourite category and then on the favourite page the image should be displayed. I am having an issue with not knowing how to dynamically get the value out of the image and add them to favourite category without loosing the pervious value. I think the way I wrote states seems messed up. I write in typescript and use material UI
SearchResults
const [images, setImages] = useState([]);
const [favImage, setFavImage] = useState<ImageProps>({
id: '',
farm: 0,
server: '',
secret: '',
title: ''
});
const handleFavImage = ({ farm, server, id, secret, title }: ImageProps): void => {
setFavImage({ id, farm, server, secret, title });
};
ImageModal
const handleFavourite = (): void => {
setIcon(!icon);
if (!icon === true && handleFavImage !== undefined) {
handleFavImage({ farm, server, id, secret, title });
}
};
Use Image Id as reference.
Use redux store / local storage to store the fav image Ids.
Assuming you have all the images here:
const [images, setImages] = useState([]);
On click of the Icon, pass Image Id of the selected Image and iterate through the images array to find the image props.
just to make you understand how to use the component state array do to this operation:
Lets create a state which will store fav image ids
const [favImages, setFavImages] = useState([]);
On Click will look something like this
const handleOnClick = (imageId) => {
var arr = [...favImages]; //copy array by value
arr.push(imageId)
setFavImages(arr) //override state with new values
}
Now the favImages array has the list of fav image ids.
Now, use this favImages in your favourite page component.
let favImageProps = props.images.filter(image => props.favImages.indexOf(image.id) !== -1)
I had the restriction for not using var and let in the code. So I had to change the code.
const [images, setImages] = useState([]);
const [favImage, setFavImage] = useState<ImageProps[]>([]);
const handleFavImage = ({ farm, server, id, secret, title }: ImageProps): void => {
setFavImage([...favImage, { id, farm, server, secret, title }]);
};
Does anyone have a way to delete id before passing through url?
I want to remove the id that is in the addedItems array.
Delete only id. Without interfering with the value inside
onFinish = async(values) => {
const { addedItems, total } = this.state;
values.re_total = total;
const { data, result } = this.props.loginReducer;
await addedItems.forEach((doc) => {
doc.car_number = values.cus_car_number;
doc.reference = values.re_reference;
doc.detail = values.re_detail;
doc.adName = result.data.u_fname;
doc.total = doc.price * doc.quantity;
});
delete addedItems.id;
console.log(addedItems)
await httpClient
.post(`http://localhost:8085/api/v1/revenue/revenue`,{addedItems})
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log("Error :", error);
})
message.success({ content: 'บันทึกรายรับเรียบร้อย!', duration: 2, style: {
marginTop: '5vh',
}} ,100);
await this.props.history.goBack();
};
I'm assuming that the id is in each item of the addedItems array. Rather than it being added to the array directly (addedItems.id = 'bad').
I switched from using a Array#forEach to Array#map so that it creates a new array. Then I use object destructuring with the rest operator (...). So I can pull the id out of the doc object and make a shallow copy of the doc at the same time. The shallow copy is important so we don't accidentally modify state values without meaning to.
That's actually the biggest issue in your code as is - it's modifying the this.state.addedItems unintentionally, as you are changing the docs without doing a shallow copy first.
As for the code: Replace the await addedItems.forEach() with this. You didn't actually need an await there, as Array#forEach doesn't return anything let alone a promise.
const mappedItems = addedItems.map(({id,...doc})=>{
doc.car_number = values.cus_car_number;
doc.reference = values.re_reference;
doc.detail = values.re_detail;
doc.adName = result.data.u_fname;
return doc;
})
Then use {addedItems: mappedItems} as the body of the httpClient.post. So that you use the new array instead of the old one pulled from the state.
I am looking for a way to handle object data from the server, given data I'd like to pass all the data into a thing, and specify one thing and return that thing, for instance if I have a organization object and I wanted the createdAt date formatted. I would just be able to pass <data.Organization.createdAt {...organization}>. Is there anything that does this?
Here's the data
const contact = {
name: 'Thomas',
uuid: 1234
}
const orgnization = {
contact,
}
const scopedOrgniazation = {
orgnization
}
Here's the Components
import {RF, SalmonLink} from '../index'
const Contact = {
Name: ({ name }) => (name) ? <RF>{name}</RF> : <NA/>,
NameLink: ({ name, uuid }) => (uuid && name) ? <SalmonLink to={`/admin/organizations/${uuid}`}>{name}<SalmonLink> : <NA/>
}
const Organization = {
Contact
}
const ScopedOrganization = {
Organization
}
Desired Components API
I was thinking this may be possible by using something like lodash's flow or flowRight.
<Contact.Name {...contact}>
<Organization.Contact.Name {...organization}>
<ScopedOrganization.Organization.Contact.Name {...scopedOrgniazation}>