I'm using redux saga with firebase in an app. When I need to use the firebase storage and I need to get the download URL of a file I just upload:
this doesn't work
yield call (uploadTask.snapshot.ref.getDownloadURL);
but this work
yield call (() => uploadTask.snapshot.ref.getDownloadURL());
can anyone help me understand why the first option isn't working? I didn't understand the difference between these approaches :)
The difference between those is what value this has once getDownloadURL is running. The first version will have this equal to the window object (in non-strict mode) or undefined (in strict mode), while the latter will have this equal to uploadTask.snapshot.ref.
The call effect does have a couple of overloads which let you specify this. You can see them listed here, but one example is to pass in an array as the first argument, as in:
yield call([uploadTask.snapshot.ref, uploadTask.snapshot.ref.getDownloadURL])
Related
I am still getting the hang of Firebase and Cloud functions, but here is what I'm trying to figure out.
The current setup
My app has a cloud function that will take a PDF that has been uploaded into a storage bucket and convert it into PNG. It doesn't destroy the original PDF, so I am left with both files.
The URL for the newly created PNG is then attached to a property on one of our documents in Firestore.
What I am trying to accomplish
I want to be able to upload a new PDF to use as a replacement image. I think I am running into a race condition where the cloud function hasn't finished executing by the time I am trying to call updateDoc() with the new PNG.
On the client side, I have the storageRef returned from the upload method:
uploadFunction(...).then((snapshot) => {
return snapshot.ref
}
I'm saving the result of this function to a variable, and I am trying to pass that into the update method that will adjust the property on my document in Firestore:
const storageRef = await functionThatUploadsPDF(file);
updateDocumentInFirestore(storageRef);
Within updateDocumentInFirestore, I'm trying to navigate to the new reference that should exist once the cloud function has finished, get a download URL, and update that property on my document:
const newImageRef = ref(storageRef.parent, "generatedImage.png");
const newDownloadURL = getDownloadURL(newImageRef).then((url) => {
updateDoc(documentRef, backgroundImage: url);
});
However, I am getting the following error - I believe due to the cloud function having not finished yet:
Firebase Storage: Object 'storage-bucket/generatedImage.png' does not exist. (storage/object-not-found)
My thoughts on potential solutions
I could try to poll the storage for the existence of generatedImage.png until the getDownloadURL call returns an actual URL, but I worry about the amount of calls this would yield.
If there is a way for the cloud function to send a message to let me know that the conversion is finished, I can send a call once for the download URL after receiving said message. However, I can't figure out how to accomplish this.
Efforts so far
I have been pursuing course 1. So far, but have not met any success yet. Scouring through Firebase documentation, I haven't been able to find any supporting resources on how to accomplish 1 or 2. Does anyone have any suggestions - either on my planned courses of action, or a new option that I haven't considered?
You can use this onFinalize trigger to send a message or update a document in Firestore to indicate that the function has finished running. This trigger is triggered whenever a file is created or updated.
onFinalize Sent when a new object (or a new generation of an existing
object) is successfully created in the bucket. This includes copying
or rewriting an existing object. A failed upload does not trigger this
event.
you can also create a promise that resolves when the downloadURL is not null, and use that promise in your updateDocumentInFirestore function. This way, the updateDoc function will only be called once the downloadURL is available.
Additionally, as was mentioned in the comments, you can consider cloud workflow.The exact implementation will depend on your specific use case
You can also check these similar cases
Firebase Storage: Object does not exist
Error: storage/object-not-found when trying to upload large image file
Firebase Storage Put could not get object
Hello this is my first question. I am trying to set up a project where modules along with the redux and sagas will be injected into the main app, using redux-injectors. In my sagas I want to use yield select, to check if an action has updated the state and then carry on. For example, when I post an image, I want to make sure there were no errors in posting the file and then move on. I use the following function:
export const imageErrors = (state: RootState): IImagesErrorState => state.image.errors
and then in the saga.ts file I use it as such:
if (imagesErrors?.postImageError !== null) {
throw imagesErrors.postImageError
}
this works fine as long as the state.image exists in the root state from the beginning. However, how do I do that when I want to inject this state later on using useInjectReducer and useInjectSaga? I obviously get an error
Property 'image' does not exist on type 'Reducer<CombinedState<{ user: CombinedState<{ auth: IAuthState; errors: IErrorState; }>; }>, AnyAction>'.ts(2339)
So how do we handle selectors of specific pieces of state, since state does not yet include them?
Thank you so much.
Can't talk about the Typescript part of things, but in terms of architecture you've got two options.
One is the obvious - that is to add conditions or ? everywhere to avoid errors from accessing missing properties, but that can get tedious quickly.
The other probably better option is to rethink your state & application chunks. What is this saga that is accessing state that isn't existing yet? Does it need to run before you have such state? If not, let's move the saga to the same chunk as the reducer. In the opposite case, where you need the saga to be running e.g. as part of the runtime chunk, then perhaps the image state should be in the runtime chunk as well.
I am trying to place a date time picker in my form. However, I get an error that says Invariant Violation, Target is not in the DOM. I am very new to javascript as well as meteor and react. What does this error mean and how can I fix it?
Here is the code: https://gist.github.com/drwofle21/8cffed99312711ddfb3f
As the comment on the Gist said "document.getElementsByClassName("calendar") Will return an array, not single node."
That array contains a list of nodes that you can loop through. Best thing to do is console log it out and see what it's actually returning and how you can best handle it.
Simple question:
Will all obersvers automatically removed when I use goOffline (disconnect to firebase) ?
If not, is there another way to do it, because removeAllOberserves doesn't seem to work or must I keep an array of single handles?
UPDATE
I answer myself.
removeAllOberserves works well, if you call it with the reference you used to set the observer!
Example:
Firebase *userThreadRef;
userThreadRef = [userRef appendPathComponent: ThreadsPath];
[userThreadRef observeEventType: FEventTypeChildAdded withBlock: ^(FDataSnapshot *snapshot) {
...
}];
....
[userThreadRef removeAllObservers];
Do not use a new reference like this:
Firebase *newUserThreadRef = [userRef appendPathComponent: ThreadsPath];
[newUserThreadRef removeAllObservers];
Will all observers automatically removed when I use goOffline (disconnect to firebase) ?
No. Calling goOffline() will not automatically remove observers/listeners.
is there another way to do it, because removeAllOberserves doesn't seem to work or must I keep an array of single handles?
It's hard to say without seeing your code, but likely your expectations are just wrong.
You'll need to call removeAllObservers() on each reference. The All in the method name is for the fact that it removes the observers for all event types, not for all references.
I am kind of confused about which methods belong with and when to use them.
Right now, I am using subscribe for basically everything and it is not working for me when I want a quick static value out of Firebase. Can you explain when I should use subscribe vs other methods other than for a strict observable?
When working with async values you have a few options: promises, rxjs, callbacks. Every option has its own drawbacks.
When you want to retrieve a single async value it is tempting to use promises for their simplicity (.then(myVal => {})). But this does not give you access to things like timeouts/throttling/retry behaviour etc. Rx streams, even for single values do give you these options.
So my recommendation would be, even if you want to have a single value, to use Observables. There is no async option for 'a quick static value out of a remote database'.
If you do not want to use the .subscribe() method there are other options which let you activate your subscription like .toPromise() which might be easier for retrieving a single value using Rx.
const getMyObjPromise = $firebase.get(myObjId)
.timeout(5000)
.retry(3)
.toPromise()
getMyObjPromise.then(obj => console.log('got my object'));
My guess is, that you have a subscribe method that contains a bunch of logic like it was a ’.then’ and you save the result to some local variable.
First: try to avoid any logic inside the subscription-method -> use stream-operators before that and then subscribe just to retrieve the data.
You much more flexible with that and it is much easier to unit-test those single parts of your stream than to test a whole component in itself.
Second: try to avoid using a manual subscriptions at all - in angular controllers they are prone to cause memory leaks if not unsubscribed.
Use the async-pipe instead in your template and let angular manage the subscription itself.