flight
flight10mo ago

Search & filter

I have the code below, but i seem to be getting an error on the args.name Property 'withSearchIndex' does not exist on type
export const getParts = query({
args: {
locationId: v.id("locations"),
factoryId: v.id("factories"),
machineClassId: v.id("machineClasses"),
name: v.string(),
},
async handler(ctx, args) {
// Required filter by locationId
let partsQuery = ctx.db
.query("parts")
.withIndex("by_locationId", (q) => q.eq("locationId", args.locationId));

// Optional filter by machineClassId
if (args.machineClassId) {
partsQuery = partsQuery.filter((q) =>
q.eq(q.field("machineClassId"), args.machineClassId)
);
}

// Optional filter by factoryId
if (args.factoryId) {
partsQuery = partsQuery.filter((q) =>
q.eq(q.field("factoryId"), args.factoryId)
);
}

// Optional search by name
if (args.name) {
partsQuery = partsQuery
.withSearchIndex("by_name", (q) => q.match("name", args.name), {
searchField: "name",
});
}

const parts = await partsQuery.collect();

return parts;
},
});
export const getParts = query({
args: {
locationId: v.id("locations"),
factoryId: v.id("factories"),
machineClassId: v.id("machineClasses"),
name: v.string(),
},
async handler(ctx, args) {
// Required filter by locationId
let partsQuery = ctx.db
.query("parts")
.withIndex("by_locationId", (q) => q.eq("locationId", args.locationId));

// Optional filter by machineClassId
if (args.machineClassId) {
partsQuery = partsQuery.filter((q) =>
q.eq(q.field("machineClassId"), args.machineClassId)
);
}

// Optional filter by factoryId
if (args.factoryId) {
partsQuery = partsQuery.filter((q) =>
q.eq(q.field("factoryId"), args.factoryId)
);
}

// Optional search by name
if (args.name) {
partsQuery = partsQuery
.withSearchIndex("by_name", (q) => q.match("name", args.name), {
searchField: "name",
});
}

const parts = await partsQuery.collect();

return parts;
},
});
31 Replies
erquhart
erquhart10mo ago
You can't apply multiple indexes - you're using withIndex() before the conditionals. you can add locationId as a filter field on your search index if you want to effectively have these two indexes combined, but you'll still need to not run withIndex() before withSearchIndex(), either one or the other.
flight
flightOP10mo ago
what would you advise i do...i dont understand when you say combine... an still not run withIndex.. I just picked up convex and im liking it so far
erquhart
erquhart10mo ago
Ah, welcome to the Convex community! So you're initially defining partsQuery using .withIndex(). Once you use withIndex() or withSearchIndex(), the object you get back no longer has the withIndex() or withSearchIndex() methods. I would honestly just write the whole query in each conditional rather than trying to initialize at the top and then extend conditionally. Here's what that looks like:
export const getParts = query({
args: {
locationId: v.id('locations'),
factoryId: v.id('factories'),
machineClassId: v.id('machineClasses'),
name: v.string(),
},
async handler(ctx, args) {
// Filter by machineClassId
if (args.machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', args.locationId))
.filter((q) => q.eq(q.field('machineClassId'), args.machineClassId))
.collect()
}

// Filter by factoryId
if (args.factoryId) {
return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', args.locationId))
.filter((q) => q.eq(q.field('factoryId'), args.factoryId))
.collect()
}

// Optional search by name
if (args.name) {
return ctx.db
.query('parts')
.withSearchIndex('by_name', (q) => q.search('name', args.name))
.filter((q) => q.eq(q.field('factoryId'), args.factoryId))
.collect()
}
},
})
export const getParts = query({
args: {
locationId: v.id('locations'),
factoryId: v.id('factories'),
machineClassId: v.id('machineClasses'),
name: v.string(),
},
async handler(ctx, args) {
// Filter by machineClassId
if (args.machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', args.locationId))
.filter((q) => q.eq(q.field('machineClassId'), args.machineClassId))
.collect()
}

// Filter by factoryId
if (args.factoryId) {
return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', args.locationId))
.filter((q) => q.eq(q.field('factoryId'), args.factoryId))
.collect()
}

// Optional search by name
if (args.name) {
return ctx.db
.query('parts')
.withSearchIndex('by_name', (q) => q.search('name', args.name))
.filter((q) => q.eq(q.field('factoryId'), args.factoryId))
.collect()
}
},
})
flight
flightOP10mo ago
You are godlike... thank you
erquhart
erquhart10mo ago
Your original also had { searchField: 'name' } - that belongs in the schema definition Going beyond your original question, understand that filter() does not limit the number of rows scanned, so you'll want to index instead of filter wherever possible. For the code you pasted, you can replace both filters with indexes. I'll give an example of that too. Actually I'm curious if kapa (ai bot in the discord here) can rewrite this, going to try that
flight
flightOP10mo ago
Im checking the code you sent, it doesnt seem that i can query both machineclass and factory at the same time... the current logic is at any time i can search for either the machineclass or factory or both
erquhart
erquhart10mo ago
yep, kapa found that lol, I didn't catch that you were conditionally applying both filters
flight
flightOP10mo ago
this is my current schema
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
factories: defineTable({
location: v.id("locations"),
name: v.string(),
}).index("by_locationId", ["location"]),
locations: defineTable({ name: v.string() }),
roles: defineTable({ role: v.string() }),
machineClasses: defineTable({
name: v.string(),
factory: v.id("factories"),
}),
parts: defineTable({
name: v.string(),
locationId: v.id("locations"),
machineClassId: v.id("machineClasses"),
factoryId: v.id("factories"),
time: v.string(),
weight: v.string(),
})
.index("by_locationId", ["locationId"])
.index("by_factoryId", ["factoryId"])
.index("by_machineClassId", ["machineClassId"])
.searchIndex("by_name", {
searchField: "name",
}),
});
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
factories: defineTable({
location: v.id("locations"),
name: v.string(),
}).index("by_locationId", ["location"]),
locations: defineTable({ name: v.string() }),
roles: defineTable({ role: v.string() }),
machineClasses: defineTable({
name: v.string(),
factory: v.id("factories"),
}),
parts: defineTable({
name: v.string(),
locationId: v.id("locations"),
machineClassId: v.id("machineClasses"),
factoryId: v.id("factories"),
time: v.string(),
weight: v.string(),
})
.index("by_locationId", ["locationId"])
.index("by_factoryId", ["factoryId"])
.index("by_machineClassId", ["machineClassId"])
.searchIndex("by_name", {
searchField: "name",
}),
});
Yeah that was the goal
erquhart
erquhart10mo ago
alright, one sec your query has all args required - are any of them optional? Guessing locationId is required and the others are optional
flight
flightOP10mo ago
args: { locationId: v.id("locations"), factoryId: v.optional(v.id("factories")), machineClassId: v.optional(v.id("machineClasses")), name: v.optional(v.string()), },
erquhart
erquhart10mo ago
This is lengthy but it lays things out in an obvious way. I would advocate for letting code be lengthy while you're getting the hang of Convex, and going back to optimize / dry once things are flowing.
export const getParts = query({
args: {
locationId: v.id('locations'),
factoryId: v.optional(v.id('factories')),
machineClassId: v.optional(v.id('machineClasses')),
name: v.optional(v.string()),
},
async handler(ctx, args) {
// Search if name is provided
if (args.name && args.factoryId && args.machineClassId) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId_machineClassId', (q) =>
q
.search('name', args.name)
.eq('locationId', args.locationId)
.eq('factoryId', args.factoryId)
.eq('machineClassId', args.machineClassId),
)
.collect()
}

if (args.name && args.factoryId) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId', (q) =>
q.search('name', args.name).eq('locationId', args.locationId),
)
.collect()
}

if (args.name && args.machineClassId) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_machineClassId', (q) =>
q.search('name', args.name).eq('locationId', args.locationId),
)
.collect()
}

if (args.name) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId', (q) =>
q.search('name', args.name).eq('locationId', args.locationId),
)
.collect()
}

// Just collect if no name is provided
if (args.factoryId && args.machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId_machineClassId', (q) =>
q
.eq('locationId', args.locationId)
.eq('factoryId', args.factoryId)
.eq('machineClassId', args.machineClassId),
)
.collect()
}

if (args.factoryId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId', (q) =>
q.eq('locationId', args.locationId).eq('factoryId', args.factoryId),
)
.collect()
}

if (args.machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_machineClassId', (q) =>
q
.eq('locationId', args.locationId)
.eq('machineClassId', args.machineClassId),
)
.collect()
}

return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', args.locationId))
.collect()
},
})
export const getParts = query({
args: {
locationId: v.id('locations'),
factoryId: v.optional(v.id('factories')),
machineClassId: v.optional(v.id('machineClasses')),
name: v.optional(v.string()),
},
async handler(ctx, args) {
// Search if name is provided
if (args.name && args.factoryId && args.machineClassId) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId_machineClassId', (q) =>
q
.search('name', args.name)
.eq('locationId', args.locationId)
.eq('factoryId', args.factoryId)
.eq('machineClassId', args.machineClassId),
)
.collect()
}

if (args.name && args.factoryId) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId', (q) =>
q.search('name', args.name).eq('locationId', args.locationId),
)
.collect()
}

if (args.name && args.machineClassId) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_machineClassId', (q) =>
q.search('name', args.name).eq('locationId', args.locationId),
)
.collect()
}

if (args.name) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId', (q) =>
q.search('name', args.name).eq('locationId', args.locationId),
)
.collect()
}

// Just collect if no name is provided
if (args.factoryId && args.machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId_machineClassId', (q) =>
q
.eq('locationId', args.locationId)
.eq('factoryId', args.factoryId)
.eq('machineClassId', args.machineClassId),
)
.collect()
}

if (args.factoryId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId', (q) =>
q.eq('locationId', args.locationId).eq('factoryId', args.factoryId),
)
.collect()
}

if (args.machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_machineClassId', (q) =>
q
.eq('locationId', args.locationId)
.eq('machineClassId', args.machineClassId),
)
.collect()
}

return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', args.locationId))
.collect()
},
})
Here's the schema with additional indexes:
export default defineSchema({
factories: defineTable({
location: v.id('locations'),
name: v.string(),
}).index('by_locationId', ['location']),
locations: defineTable({ name: v.string() }),
roles: defineTable({ role: v.string() }),
machineClasses: defineTable({
name: v.string(),
factory: v.id('factories'),
}),
parts: defineTable({
name: v.string(),
locationId: v.id('locations'),
machineClassId: v.id('machineClasses'),
factoryId: v.id('factories'),
time: v.string(),
weight: v.string(),
})
.index('by_locationId', ['locationId'])
.index('by_factoryId', ['factoryId'])
.index('by_machineClassId', ['machineClassId'])
.index('by_locationId_factoryId', ['locationId', 'factoryId'])
.index('by_locationId_machineClassId', ['locationId', 'machineClassId'])
.index('by_locationId_factoryId_machineClassId', [
'locationId',
'factoryId',
'machineClassId',
])
.searchIndex('by_name_locationId', {
searchField: 'name',
filterFields: ['locationId'],
})
.searchIndex('by_name_locationId_factoryId', {
searchField: 'name',
filterFields: ['locationId', 'factoryId'],
})
.searchIndex('by_name_locationId_machineClassId', {
searchField: 'name',
filterFields: ['locationId', 'machineClassId'],
})
.searchIndex('by_name_locationId_factoryId_machineClassId', {
searchField: 'name',
filterFields: ['locationId', 'factoryId', 'machineClassId'],
}),
})
export default defineSchema({
factories: defineTable({
location: v.id('locations'),
name: v.string(),
}).index('by_locationId', ['location']),
locations: defineTable({ name: v.string() }),
roles: defineTable({ role: v.string() }),
machineClasses: defineTable({
name: v.string(),
factory: v.id('factories'),
}),
parts: defineTable({
name: v.string(),
locationId: v.id('locations'),
machineClassId: v.id('machineClasses'),
factoryId: v.id('factories'),
time: v.string(),
weight: v.string(),
})
.index('by_locationId', ['locationId'])
.index('by_factoryId', ['factoryId'])
.index('by_machineClassId', ['machineClassId'])
.index('by_locationId_factoryId', ['locationId', 'factoryId'])
.index('by_locationId_machineClassId', ['locationId', 'machineClassId'])
.index('by_locationId_factoryId_machineClassId', [
'locationId',
'factoryId',
'machineClassId',
])
.searchIndex('by_name_locationId', {
searchField: 'name',
filterFields: ['locationId'],
})
.searchIndex('by_name_locationId_factoryId', {
searchField: 'name',
filterFields: ['locationId', 'factoryId'],
})
.searchIndex('by_name_locationId_machineClassId', {
searchField: 'name',
filterFields: ['locationId', 'machineClassId'],
})
.searchIndex('by_name_locationId_factoryId_machineClassId', {
searchField: 'name',
filterFields: ['locationId', 'factoryId', 'machineClassId'],
}),
})
flight
flightOP10mo ago
Thank you so much for the code... really means a lot.. im slowly but surely understanding things as they go
erquhart
erquhart10mo ago
For sure. You'll want to utilizing indexes as much as possible so each query is scanning the smallest possible amount of rows, keeps things snappy.
flight
flightOP10mo ago
oh okay... i think i understand
erquhart
erquhart10mo ago
Looks like there's an error around search index uniqueness, looking at that now ah right, you can only search index on a given field once per table hmm
flight
flightOP10mo ago
yeah was just about to say that
erquhart
erquhart10mo ago
Playing with combining the filters now - I believe they have to be in order, which would make undefined a value in the index for missing fields, but that isn't what you want But maybe that's not the case, confirming
if (args.name) {
const name = args.name
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId_machineClassId', (q) => {
let search = q.search('name', name).eq('locationId', args.locationId)
if (args.factoryId) {
search = search.eq('factoryId', args.factoryId)
}
if (args.machineClassId) {
search = search.eq('machineClassId', args.machineClassId)
}
return search
})
.collect()
}
if (args.name) {
const name = args.name
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId_machineClassId', (q) => {
let search = q.search('name', name).eq('locationId', args.locationId)
if (args.factoryId) {
search = search.eq('factoryId', args.factoryId)
}
if (args.machineClassId) {
search = search.eq('machineClassId', args.machineClassId)
}
return search
})
.collect()
}
That can replace the three args.name queries And you can drop the other search indexes Working:
// schema
export default defineSchema({
factories: defineTable({
location: v.id('locations'),
name: v.string(),
}).index('by_locationId', ['location']),
locations: defineTable({ name: v.string() }),
roles: defineTable({ role: v.string() }),
machineClasses: defineTable({
name: v.string(),
factory: v.id('factories'),
}),
parts: defineTable({
name: v.string(),
locationId: v.id('locations'),
machineClassId: v.id('machineClasses'),
factoryId: v.id('factories'),
time: v.string(),
weight: v.string(),
})
.index('by_locationId', ['locationId'])
.index('by_factoryId', ['factoryId'])
.index('by_machineClassId', ['machineClassId'])
.index('by_locationId_factoryId', ['locationId', 'factoryId'])
.index('by_locationId_machineClassId', ['locationId', 'machineClassId'])
.index('by_locationId_factoryId_machineClassId', [
'locationId',
'factoryId',
'machineClassId',
])
.searchIndex('by_name_locationId_factoryId_machineClassId', {
searchField: 'name',
filterFields: ['locationId', 'factoryId', 'machineClassId'],
}),
})

// query
export const getParts = query({
args: {
locationId: v.id('locations'),
factoryId: v.optional(v.id('factories')),
machineClassId: v.optional(v.id('machineClasses')),
name: v.optional(v.string()),
},
async handler(ctx, { name, locationId, factoryId, machineClassId }) {
// Search if name is provided
if (name) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId_machineClassId', (q) => {
let search = q.search('name', name).eq('locationId', locationId)
if (factoryId) {
search = search.eq('factoryId', factoryId)
}
if (machineClassId) {
search = search.eq('machineClassId', machineClassId)
}
return search
})
.collect()
}

// Just collect if no name is provided
if (factoryId && machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId_machineClassId', (q) =>
q
.eq('locationId', locationId)
.eq('factoryId', factoryId)
.eq('machineClassId', machineClassId),
)
.collect()
}

if (factoryId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId', (q) =>
q.eq('locationId', locationId).eq('factoryId', factoryId),
)
.collect()
}

if (machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_machineClassId', (q) =>
q
.eq('locationId', locationId)
.eq('machineClassId', machineClassId),
)
.collect()
}

return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', locationId))
.collect()
},
})
// schema
export default defineSchema({
factories: defineTable({
location: v.id('locations'),
name: v.string(),
}).index('by_locationId', ['location']),
locations: defineTable({ name: v.string() }),
roles: defineTable({ role: v.string() }),
machineClasses: defineTable({
name: v.string(),
factory: v.id('factories'),
}),
parts: defineTable({
name: v.string(),
locationId: v.id('locations'),
machineClassId: v.id('machineClasses'),
factoryId: v.id('factories'),
time: v.string(),
weight: v.string(),
})
.index('by_locationId', ['locationId'])
.index('by_factoryId', ['factoryId'])
.index('by_machineClassId', ['machineClassId'])
.index('by_locationId_factoryId', ['locationId', 'factoryId'])
.index('by_locationId_machineClassId', ['locationId', 'machineClassId'])
.index('by_locationId_factoryId_machineClassId', [
'locationId',
'factoryId',
'machineClassId',
])
.searchIndex('by_name_locationId_factoryId_machineClassId', {
searchField: 'name',
filterFields: ['locationId', 'factoryId', 'machineClassId'],
}),
})

// query
export const getParts = query({
args: {
locationId: v.id('locations'),
factoryId: v.optional(v.id('factories')),
machineClassId: v.optional(v.id('machineClasses')),
name: v.optional(v.string()),
},
async handler(ctx, { name, locationId, factoryId, machineClassId }) {
// Search if name is provided
if (name) {
return ctx.db
.query('parts')
.withSearchIndex('by_name_locationId_factoryId_machineClassId', (q) => {
let search = q.search('name', name).eq('locationId', locationId)
if (factoryId) {
search = search.eq('factoryId', factoryId)
}
if (machineClassId) {
search = search.eq('machineClassId', machineClassId)
}
return search
})
.collect()
}

// Just collect if no name is provided
if (factoryId && machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId_machineClassId', (q) =>
q
.eq('locationId', locationId)
.eq('factoryId', factoryId)
.eq('machineClassId', machineClassId),
)
.collect()
}

if (factoryId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_factoryId', (q) =>
q.eq('locationId', locationId).eq('factoryId', factoryId),
)
.collect()
}

if (machineClassId) {
return ctx.db
.query('parts')
.withIndex('by_locationId_machineClassId', (q) =>
q
.eq('locationId', locationId)
.eq('machineClassId', machineClassId),
)
.collect()
}

return ctx.db
.query('parts')
.withIndex('by_locationId', (q) => q.eq('locationId', locationId))
.collect()
},
})
Some gotchas to point out: - "filter" when querying with a regular index does not limit the rows scanned - "filterFields", defined in a search index, do limit the rows scanned - when working with a regular index, queries must match indexed fields in the order they're defined in the schema, and cannot skip fields (so q.eq('field1', field1).eq('field3', field3) doesn't work if the index is defined as .index('by_field', ['field1', 'field2', 'field3']) because field2 is skipped) - when working with the filter fields against a search index, queries can use search.eq() to reference any of the filter fields in any order
flight
flightOP10mo ago
this is amazing.. i think i get it... you are amazing.. thanks for taking the time to explain
erquhart
erquhart10mo ago
No problem! Convex is the bees knees, I hope you love it. Reach out here in support if you get stuck or have questions 👍
flight
flightOP10mo ago
@erquhart Hello chief, been wondering what is the quickest way to get data from an external id...for instance this query.. this query returns the machineClassId... i want to be able to get the info from the id
return ctx.db
.query("parts")
.withIndex("by_locationId", (q) => q.eq("locationId", locationId))
.collect();
return ctx.db
.query("parts")
.withIndex("by_locationId", (q) => q.eq("locationId", locationId))
.collect();
erquhart
erquhart10mo ago
You'll map/loop over the data to get related data - this is how joins work in Convex:
const parts = await ctx.db
.query("parts")
.withIndex("by_locationId", (q) => q.eq("locationId", locationId))
.collect();

return Promise.all(parts.map(part => {
const machineClass = await ctx.db.get(part.machineClassId)
return { ...part, machineClass }
})
const parts = await ctx.db
.query("parts")
.withIndex("by_locationId", (q) => q.eq("locationId", locationId))
.collect();

return Promise.all(parts.map(part => {
const machineClass = await ctx.db.get(part.machineClassId)
return { ...part, machineClass }
})
flight
flightOP10mo ago
@erquhart thanks as always... another confusing thing has to be pagination on how to use the usePaginatedQuery
erquhart
erquhart10mo ago
No problem! Did you see the docs on paginated queries?
flight
flightOP10mo ago
yeah i did const { results, status, isLoading, loadMore } = usePaginatedQuery( api.messages.list, { channel: "#general" }, { initialNumItems: 5 } ); i already have the query you sent me... it was how to integrate it that became the issue this is what my query looks like now, i want to know if this is ideal
if (factoryId && machineClassId) {
const parts = await ctx.db
.query("parts")
.withIndex("by_locationId_factoryId_machineClassId", (q) =>
q
.eq("locationId", locationId)
.eq("factoryId", factoryId)
.eq("machineClassId", machineClassId)
)
.paginate(paginationOpts);

return Promise.all(
parts.page.map(async (part) => {
const machineClass = await ctx.db.get(part.machineClassId);
return { ...part, machineClass };
})
);
}

if (factoryId) {
const parts = await ctx.db
.query("parts")
.withIndex("by_locationId_factoryId", (q) =>
q.eq("locationId", locationId).eq("factoryId", factoryId)
)
.paginate(paginationOpts);

return Promise.all(
parts.page.map(async (part) => {
const machineClass = await ctx.db.get(part.machineClassId);
return { ...part, machineClass };
})
);
}
if (factoryId && machineClassId) {
const parts = await ctx.db
.query("parts")
.withIndex("by_locationId_factoryId_machineClassId", (q) =>
q
.eq("locationId", locationId)
.eq("factoryId", factoryId)
.eq("machineClassId", machineClassId)
)
.paginate(paginationOpts);

return Promise.all(
parts.page.map(async (part) => {
const machineClass = await ctx.db.get(part.machineClassId);
return { ...part, machineClass };
})
);
}

if (factoryId) {
const parts = await ctx.db
.query("parts")
.withIndex("by_locationId_factoryId", (q) =>
q.eq("locationId", locationId).eq("factoryId", factoryId)
)
.paginate(paginationOpts);

return Promise.all(
parts.page.map(async (part) => {
const machineClass = await ctx.db.get(part.machineClassId);
return { ...part, machineClass };
})
);
}
i cant paste the full code because discord wouldnt allow me but im having an issue when trying to query im getting Types of property _returnType are incompatible. when calling it like this
const { results, status, loadMore } = usePaginatedQuery(api.parts.getParts, {
locationId: activeLocation as Id<'locations'>,
factoryId,
machineClassId,
name,
}, {
initialNumItems: 5
});
const { results, status, loadMore } = usePaginatedQuery(api.parts.getParts, {
locationId: activeLocation as Id<'locations'>,
factoryId,
machineClassId,
name,
}, {
initialNumItems: 5
});
@erquhart id appreciate your feedback when you are less busy.... apologies for the disturbances
erquhart
erquhart10mo ago
hey! no problem at all, notifications don't hit me unless I want them to catching up on your messages now Yeah, .paginate() returns a result object - is typescript not showing you errors in the convex function you pasted above? oh nvm you're hitting parts.page, that's correct missed that hmm what is it saying are the two incompatible return types oh hmm you're calling paginate twice in the same convex function, that won't work I'm not coming up with a better way than using two separate query functions here, one with machineClassId and one without. When the team comes online they may have a way to do it better, but I can't think of one. Although as long as the arguments don't change I feel like what you have should work, as it would consistently hit the same conditional. If you can share the error that would help illuminate a bit.
flight
flightOP10mo ago
i am confused about the calling pagination twice part @erquhart this is the error Argument of type 'FunctionReference<"query", "public", { factoryId?: Id<"factories"> | undefined; machineClassId?: Id<"machineClasses"> | undefined; name?: string | undefined; locationId: Id<"locations">; paginationOpts: { ...; }; }, { ...; }[]>' is not assignable to parameter of type 'PaginatedQueryReference'. Types of property '_returnType' are incompatible. Type '{ machineClass: { _id: Id<"machineClasses">; _creationTime: number; name: string; factory: Id<"factories">; } | null; _id: Id<"parts">; _creationTime: number; factoryId: Id<"factories">; ... 4 more ...; weight: number; }[]' is missing the following properties from type 'PaginationResult<any>': page, isDone, continueCursor this is how i am calling it const { results, status, loadMore } = usePaginatedQuery(api.parts.getParts, { locationId: activeLocation as Id<'locations'>, factoryId, machineClassId, name, }, { initialNumItems: 5 });
erquhart
erquhart10mo ago
oh right Your paginated query has to return the object you received from .paginate(), it includes the cursor You can still manipulate the items in the page array as you're doing, but the function ultimately needs to return the whole result object
if (factoryId && machineClassId) {
const result = await ctx.db
.query("parts")
.withIndex("by_locationId_factoryId_machineClassId", (q) =>
q
.eq("locationId", locationId)
.eq("factoryId", factoryId)
.eq("machineClassId", machineClassId)
)
.paginate(paginationOpts);

const page = await Promise.all(
result.page.map(async (part) => {
const machineClass = await ctx.db.get(part.machineClassId);
return { ...part, machineClass };
})
);

return { ...result, page }
}
if (factoryId && machineClassId) {
const result = await ctx.db
.query("parts")
.withIndex("by_locationId_factoryId_machineClassId", (q) =>
q
.eq("locationId", locationId)
.eq("factoryId", factoryId)
.eq("machineClassId", machineClassId)
)
.paginate(paginationOpts);

const page = await Promise.all(
result.page.map(async (part) => {
const machineClass = await ctx.db.get(part.machineClassId);
return { ...part, machineClass };
})
);

return { ...result, page }
}
Your query should work fine with just that change, but if the args change it will reset pagination. I believe that's the only thing to be aware of there. Pretty sure changes in args always resets pagination anyway, but I could be wrong, it may adapt. Can't recall atm But, if a call to your paginated query triggers one of those .paginate() calls, and then a change in the args leads to the other .paginate() being called, that will either cause an error, odd behavior, or just reset the pagination. Again, team will know for sure.
flight
flightOP10mo ago
thanks.. ill definitely keep that in mind
erquhart
erquhart10mo ago
Helpful answer from kapa has some relevant links: https://discord.com/channels/1019350475847499849/1237752632483774514

Did you find this page helpful?