dannyelo
dannyelo•5mo ago

Need help using the uploadstuff api

Hello, I'm trying to follow the example in the uploadstuff website. 1. I create a convex/files.ts file and paste the code provided. 2. Then I paste the code in the UploadButton to my component. Instantly I'm getting errors in the convex console.
ctx.db.insert('listingImages', {
listingId: args.listingId,
storageIds: args.uploaded.map(({ storageId }) => storageId),
// ...
})
ctx.db.insert('listingImages', {
listingId: args.listingId,
storageIds: args.uploaded.map(({ storageId }) => storageId),
// ...
})
In this code there is no uploaded arg in the args: property. Don't really understand what I need to pass. I attach an image with the errors. The someId error is not a problem, I get it. Thank you!
No description
24 Replies
dannyelo
dannyeloOP•5mo ago
I think here should be the storageIds...
ctx.db.insert('listingImages', {
storageIds: args.storageIds.map(({ storageId }) => storageId),
// ...
})
ctx.db.insert('listingImages', {
storageIds: args.storageIds.map(({ storageId }) => storageId),
// ...
})
I don't know where the variable uploaded came from
erquhart
erquhart•5mo ago
Sharing your function code would help, but it looks like the args for your mutation only include storageIds. You need to get the listingId from somewhere (maybe add to args). Oooh I see the example you're referencing - that looks like a typo, replace args.uploaded with args.storageIds It looks like you added listingId yourself, so you'll want to add that to the function args and then pass it from the call site in your client.
dannyelo
dannyeloOP•5mo ago
Yes, that was it. I suggest you fix that, because is somehow confusing
erquhart
erquhart•5mo ago
cc/ @Michal Srb
erquhart
erquhart•5mo ago
You may want to read a bit on mutation arguments and validators to better understand the code you're deploying: https://docs.convex.dev/functions/validation#adding-validators
Argument and Return Value Validation | Convex Developer Hub
Argument and return value validators ensure that
dannyelo
dannyeloOP•5mo ago
listingImages: defineTable({
listingId: v.optional(v.id('listings')),
storageIds: v.array(v.string()),
}),
listingImages: defineTable({
listingId: v.optional(v.id('listings')),
storageIds: v.array(v.string()),
}),
export const saveStorageIds = mutation({
args: {
listingId: v.string(),
storageIds: v.array(
v.object({
storageId: v.string(),
})
),
},
handler: async (ctx, args) => {
if (!args.listingId) {
return null
}
const normalizedListingId = ctx.db.normalizeId('listings', args.listingId)
if (!normalizedListingId) {
throw new Error('ListingId not exists')
}

return ctx.db.insert('listingImages', {
listingId: normalizedListingId,
storageIds: args.storageIds.map(({ storageId }) => storageId),

})

},
})
export const saveStorageIds = mutation({
args: {
listingId: v.string(),
storageIds: v.array(
v.object({
storageId: v.string(),
})
),
},
handler: async (ctx, args) => {
if (!args.listingId) {
return null
}
const normalizedListingId = ctx.db.normalizeId('listings', args.listingId)
if (!normalizedListingId) {
throw new Error('ListingId not exists')
}

return ctx.db.insert('listingImages', {
listingId: normalizedListingId,
storageIds: args.storageIds.map(({ storageId }) => storageId),

})

},
})
erquhart
erquhart•5mo ago
oh you got it 🫡
dannyelo
dannyeloOP•5mo ago
Thank you erquhart!
erquhart
erquhart•5mo ago
Are you intentionally avoiding using v.id() to validate the listing id?
dannyelo
dannyeloOP•5mo ago
Is there any benefit using the v.id()? It validates something?
erquhart
erquhart•5mo ago
It does what you're doing with normalize id
dannyelo
dannyeloOP•5mo ago
Oh, nice
erquhart
erquhart•5mo ago
I'd gather you should be able to make listingId a required field and just use the id validator for the args here.
dannyelo
dannyeloOP•5mo ago
I don't need to normalize if I use it?
erquhart
erquhart•5mo ago
Are you passing in strings that might not be valid convex ids? The convex libraries for client and server both provide id validation, so you shouldn't need to manually normalize unless it's part of a more complicated system, probably involving an external system.
dannyelo
dannyeloOP•5mo ago
No I'm not Got it Good to know that
args: {
listingId: v.id('listings'),
storageIds: v.array(
v.object({
storageId: v.string(),
})
),
args: {
listingId: v.id('listings'),
storageIds: v.array(
v.object({
storageId: v.string(),
})
),
How do you recommend to handle the error? Should I do a try catch block? If the id not exists, will throw an error?
erquhart
erquhart•5mo ago
listingImages: defineTable({
listingId: v.id('listings'),
storageIds: v.array(v.id('_storage')),
}),

export const saveStorageIds = mutation({
args: {
listingId: v.id('listings'),
storageIds: v.array(
v.object({
storageId: v.id('_storage'),
})
),
},
handler: async (ctx, args) => {
return ctx.db.insert('listingImages', {
listingId: args.listingId,
storageIds: args.storageIds.map(({ storageId }) => storageId),
})

},
})
listingImages: defineTable({
listingId: v.id('listings'),
storageIds: v.array(v.id('_storage')),
}),

export const saveStorageIds = mutation({
args: {
listingId: v.id('listings'),
storageIds: v.array(
v.object({
storageId: v.id('_storage'),
})
),
},
handler: async (ctx, args) => {
return ctx.db.insert('listingImages', {
listingId: args.listingId,
storageIds: args.storageIds.map(({ storageId }) => storageId),
})

},
})
Unless there's a scenario where the id might not exist, this should cover it. I add the v.id('_storage') bit, which I think works, but the fact that it wasn't used in the example in the docs makes me wonder if maybe it doesn't. Try replacing it with v.string() if it gives you trouble.
dannyelo
dannyeloOP•5mo ago
Ok, I'll try it Should I save the sotrageIds in one array in the table? Will that make a problem if the user wants make CRUD them? Beacuse they don't have separated Ids Thats what the example is doing
erquhart
erquhart•5mo ago
Not fully understanding your question, but it is valid to store the ids in an array. They'll remain distinct and can still be used relationally. Whether that's the right approach depends on your use case.
dannyelo
dannyeloOP•5mo ago
What if the user needs to delete an image? Every listing has multiple images. Whats the process of doing that? I need to send the image url, and the delete it with a filter function? and rewrite (replace everything) with the new array?
erquhart
erquhart•5mo ago
Yes, that's exactly the process, but it's quite simple
export const deleteListingImage = mutation({
args: {
listingId: v.id('listings'),
storageId: v.id('_storage'),
},
handler: async (ctx, args) => {
const listing = await ctx.db.get(args.listingId)
if (!listing) {
throw Error('listing not found')
}
await ctx.storage.delete(args.storageId);
const storageIds = listing.storageIds
.filter(storageId => storageId !== args.storageId)
await ctx.db.patch(args.listingId, { storageIds })
},
})
export const deleteListingImage = mutation({
args: {
listingId: v.id('listings'),
storageId: v.id('_storage'),
},
handler: async (ctx, args) => {
const listing = await ctx.db.get(args.listingId)
if (!listing) {
throw Error('listing not found')
}
await ctx.storage.delete(args.storageId);
const storageIds = listing.storageIds
.filter(storageId => storageId !== args.storageId)
await ctx.db.patch(args.listingId, { storageIds })
},
})
Convex's API is almost like manipulating in-memory storage. So simple it's confusing lol
dannyelo
dannyeloOP•5mo ago
Thank you @erquhart appreciate your time to explain!
lee
lee•5mo ago
v.id("_storage") should work (let us know if it doesn't). I think uploadstuff was created before we exposed the _storage system table, so it's just a historical reason that the best available at the time was v.string()
Michal Srb
Michal Srb•5mo ago
Docs fixed (I noticed another typo in the each component's docs). Thanks!

Did you find this page helpful?