tl;dr - I'm fairly new to building Retool apps and looking for guidance on best practices (and bad ones to watch out for! ). Particularly, how do you manage synchronous query dependencies?
Now for the context...
I'm building a fairly simple app that displays a single table connected to a Firestore database. I followed the Firebase integration documentation to get that setup and all was .
Everything worked mostly as expected. I wanted to allow the user to add a row so I went and enabled the "Show add row button" for the table and all of a sudden a user could add a row. But this is where the challenge began...
We have a field in the database schema for a mediaUrl
of an uploaded image. Obviously our table doesn't just magically know that we're storing the images in Firebase Storage.
So I added a button onto the app that opened a form that had all of the fields. Here is our form:
Doing some digging around, I found that Retool doesn't have a direct integration with Firebase Storage but that you can work around that by uploading images via the Google Cloud Storage Integration because Firebase Storage is really just GCS under the hood.
So what do we need to do on form submission to make this work?
- Generate a unique name for the image so that there are not name collisions
- Upload the image to Firebase Storage
- Generate a signed URL for the image such that it is accessible from the client
- Use the signed URL to create a new object in the Firestore database
Obviously this flow includes some synchronous dependencies. Due to the multiple steps involved, I assumed that it would be best to manage all of this flow with a single Javascript component. Question: Is using a single Javascript component as a flow controller an anti-pattern in Retool?
Initially, I tried using onSuccess
to manage the synchronous flow but wasn't happy with the look of the code and was really longing for a way to use async/await
.
More digging around and I I found this post which included a pattern for managing synchronous calls using async/await
. Giving it a try, it worked mostly as I had hoped. I ended up with the following:
(async () => {
// Generate the unique name for the file by pre-pending a timestamp onto the file name
let uploadName = `${moment.now()}-${feedItemImageDropZone.files[0].name}`;
// Trigger the query to upload the image and wait for it to complete.
await homeImageUpload.trigger({
additionalScope: {
fileNameForUpload: uploadName,
},
onFailure: (err) => console.log(`Failed homeImageUpload ${err}`),
});
await getSignedUrlForImage.trigger({
additionalScope: {
fileKey: uploadName
},
onFailure: (err) => console.log(`Failed getSignedUrlForImage ${err}`),
onSuccess: console.log(`Signed: ${getSignedUrlForImage.data.signedUrl}`),
});
await newHomeSubmit.trigger({
additionalScope: {
signedUrl: getSignedUrlForImage.data.signedUrl,
},
onFailure: (err) => console.log(`Failed newHomeSubmit ${err}`),
onSuccess: (data) => console.log(`Success: ${JSON.stringify(data)}`),
})
})();
At the bottom of this post you can see my setup for the homeImageUpload
and getSignedUrlForImage
components.
Question: Is there a more Retool-y way of going about this?
Happy to her any advice or thoughts!
homeImageUpload Setup
getSignUrlForImage Setup