ArgumentValidationError: Object is missing the required field `numItems
ERROR:
// backend
// frontend
Error: [CONVEX Q(user:getThreads)] ArgumentValidationError: Object is missing the required field `numItems`. Consider wrapping the field validator in `v.optional(...)` if this is expected.
Path: .paginationOpts
Object: {cursor: "0783738008d9b382ca0ba467ff5e71bc8eda02090a165d0dc90391a90916daa27cc2b854de242c61da89990042c8fcacc25022b6df1d9a6c9bd3fd512ee0716493d2f68a7a189e3d2a9b96acc53d75433fb28bb9fec47fa4c3cb2d1d233c25e491440c02b0fb9bf201eceb7be3e6adf9952385e8b7479930adee14cf645c1bce29e6d7081618158a856183f3d1a325", id: 1.0}
Validator: v.object({cursor: v.union(v.string(), v.null()), endCursor: v.optional(v.union(v.string(), v.null()))
Error: [CONVEX Q(user:getThreads)] ArgumentValidationError: Object is missing the required field `numItems`. Consider wrapping the field validator in `v.optional(...)` if this is expected.
Path: .paginationOpts
Object: {cursor: "0783738008d9b382ca0ba467ff5e71bc8eda02090a165d0dc90391a90916daa27cc2b854de242c61da89990042c8fcacc25022b6df1d9a6c9bd3fd512ee0716493d2f68a7a189e3d2a9b96acc53d75433fb28bb9fec47fa4c3cb2d1d233c25e491440c02b0fb9bf201eceb7be3e6adf9952385e8b7479930adee14cf645c1bce29e6d7081618158a856183f3d1a325", id: 1.0}
Validator: v.object({cursor: v.union(v.string(), v.null()), endCursor: v.optional(v.union(v.string(), v.null()))
export const getThreads = query({
args: {
districtsId: v.union(v.id("districts"), v.null()),
timeFrame: v.union(
v.literal("week"),
v.literal("month"),
v.literal("year"),
v.literal("allTime")
), // 'day', 'month', 'year', 'allTime'
filter: v.union(
v.literal("points"),
v.literal("hearts"),
v.literal("laughs"),
v.literal("likes"),
v.literal("dislikes")
),
paginationOpts: paginationOptsValidator,
},
handler: async (
{ db },
{ districtsId, timeFrame, filter, paginationOpts }
) => {
const timeFrameStart = calculateTimestamp(timeFrame);
let threads;
threads = await db
.query("threads")
// .withIndex(`by_districtsId_postDate_${filter}`, (q) =>
// q.eq("districtsId", districtsId).gte("postDate", timeFrameStart)
// )
.order("desc")
.paginate(paginationOpts);
return threads;
},
});
export const getThreads = query({
args: {
districtsId: v.union(v.id("districts"), v.null()),
timeFrame: v.union(
v.literal("week"),
v.literal("month"),
v.literal("year"),
v.literal("allTime")
), // 'day', 'month', 'year', 'allTime'
filter: v.union(
v.literal("points"),
v.literal("hearts"),
v.literal("laughs"),
v.literal("likes"),
v.literal("dislikes")
),
paginationOpts: paginationOptsValidator,
},
handler: async (
{ db },
{ districtsId, timeFrame, filter, paginationOpts }
) => {
const timeFrameStart = calculateTimestamp(timeFrame);
let threads;
threads = await db
.query("threads")
// .withIndex(`by_districtsId_postDate_${filter}`, (q) =>
// q.eq("districtsId", districtsId).gte("postDate", timeFrameStart)
// )
.order("desc")
.paginate(paginationOpts);
return threads;
},
});
const {
results: serverThreads,
status,
isLoading,
loadMore,
} = usePaginatedQuery(
api.user.getThreads,
{
districtsId: district.districtsId,
timeFrame: timeFrame,
filter: filter,
},
{ initialNumItems: 2 }
);
const {
results: serverThreads,
status,
isLoading,
loadMore,
} = usePaginatedQuery(
api.user.getThreads,
{
districtsId: district.districtsId,
timeFrame: timeFrame,
filter: filter,
},
{ initialNumItems: 2 }
);
4 Replies
where is
paginationOptsValidator
defined?paginationOptsValidator
?
// getting it from here
import { paginationOptsValidator } from "convex/server";
// ...
export const getThreads = query({
args: {
districtsId: v.union(v.id("districts"), v.null()),
timeFrame: v.number(),
// timeFrame: v.union(
// v.literal("week"),
// v.literal("month"),
// v.literal("year"),
// v.literal("allTime")
// ), // 'day', 'month', 'year', 'allTime'
filter: v.union(
v.literal("points"),
v.literal("hearts"),
v.literal("laughs"),
v.literal("likes"),
v.literal("dislikes")
),
paginationOpts: paginationOptsValidator,
},
handler: async (
{ db },
{ districtsId, timeFrame, filter, paginationOpts }
) => {
console.log("🚀 ~ paginationOpts:", paginationOpts);
let threads;
if (timeFrame === "allTime") {
threads = await db
.query("threads")
.withIndex(`by_districtsId_${filter}`, (q) =>
q.eq("districtsId", districtsId)
)
.order("desc")
.paginate(paginationOpts);
} else {
threads = await db
.query("threads")
.withIndex(`by_districtsId_postDate_${filter}`, (q) =>
q.eq("districtsId", districtsId).gte("postDate", timeFrame)
)
.order("desc")
.paginate(paginationOpts);
}
return threads;
},
});
// getting it from here
import { paginationOptsValidator } from "convex/server";
// ...
export const getThreads = query({
args: {
districtsId: v.union(v.id("districts"), v.null()),
timeFrame: v.number(),
// timeFrame: v.union(
// v.literal("week"),
// v.literal("month"),
// v.literal("year"),
// v.literal("allTime")
// ), // 'day', 'month', 'year', 'allTime'
filter: v.union(
v.literal("points"),
v.literal("hearts"),
v.literal("laughs"),
v.literal("likes"),
v.literal("dislikes")
),
paginationOpts: paginationOptsValidator,
},
handler: async (
{ db },
{ districtsId, timeFrame, filter, paginationOpts }
) => {
console.log("🚀 ~ paginationOpts:", paginationOpts);
let threads;
if (timeFrame === "allTime") {
threads = await db
.query("threads")
.withIndex(`by_districtsId_${filter}`, (q) =>
q.eq("districtsId", districtsId)
)
.order("desc")
.paginate(paginationOpts);
} else {
threads = await db
.query("threads")
.withIndex(`by_districtsId_postDate_${filter}`, (q) =>
q.eq("districtsId", districtsId).gte("postDate", timeFrame)
)
.order("desc")
.paginate(paginationOpts);
}
return threads;
},
});
console.log(":rocket: ~ paginationOpts:", paginationOpts);
is:
paginationOpts:' {
cursor: null,
id: 1,
numItems: 2
}
paginationOpts:' {
cursor: null,
id: 1,
numItems: 2
}
function useBufferedPaginatedState(
queryName,
queryArgs,
paginationOpts = { initialNumItems: 10 },
differ
) {
// Fetch the paginated data from the server.
const {
results: upstream,
status,
loadMore,
} = usePaginatedQuery(queryName, queryArgs, paginationOpts);
// State to store the current displayed value.
const [currentResults, setCurrentResults] = useState(upstream);
// State to track if the initial data has been set.
const [isInitialDataLoaded, setIsInitialDataLoaded] = useState(false);
// UseEffect to set the initial data only once.
useEffect(() => {
if (!isInitialDataLoaded && upstream?.length > 0 && upstream !== null) {
setCurrentResults(upstream);
setIsInitialDataLoaded(true);
}
}, [upstream, isInitialDataLoaded]);
// Ref to keep track of the latest data from the server.
const upstreamRef = useRef(upstream);
// Update the ref's current value every time the upstream/server value changes.
upstreamRef.current = upstream;
const doSync = () => {
console.log("sync!");
setCurrentResults(upstreamRef.current);
};
const diff = upstream ? differ(currentResults, upstream) : null;
return {
currentResults,
diff,
sync: doSync,
status,
loadMore,
};
}
// ...
const { currentResults, diff, doSync, status, loadMore } =
useBufferedPaginatedState(
api.user.getThreads,
{
/* query args */
districtsId: district.districtsId,
timeFrame: memoizedTimestamp,
filter: filter,
},
{ initialNumItems: 2 },
(oldVal, newVal) => {
// Example differ function that calculates the difference between old and new data.
return newVal.length - (oldVal?.length ?? 0);
}
);
function useBufferedPaginatedState(
queryName,
queryArgs,
paginationOpts = { initialNumItems: 10 },
differ
) {
// Fetch the paginated data from the server.
const {
results: upstream,
status,
loadMore,
} = usePaginatedQuery(queryName, queryArgs, paginationOpts);
// State to store the current displayed value.
const [currentResults, setCurrentResults] = useState(upstream);
// State to track if the initial data has been set.
const [isInitialDataLoaded, setIsInitialDataLoaded] = useState(false);
// UseEffect to set the initial data only once.
useEffect(() => {
if (!isInitialDataLoaded && upstream?.length > 0 && upstream !== null) {
setCurrentResults(upstream);
setIsInitialDataLoaded(true);
}
}, [upstream, isInitialDataLoaded]);
// Ref to keep track of the latest data from the server.
const upstreamRef = useRef(upstream);
// Update the ref's current value every time the upstream/server value changes.
upstreamRef.current = upstream;
const doSync = () => {
console.log("sync!");
setCurrentResults(upstreamRef.current);
};
const diff = upstream ? differ(currentResults, upstream) : null;
return {
currentResults,
diff,
sync: doSync,
status,
loadMore,
};
}
// ...
const { currentResults, diff, doSync, status, loadMore } =
useBufferedPaginatedState(
api.user.getThreads,
{
/* query args */
districtsId: district.districtsId,
timeFrame: memoizedTimestamp,
filter: filter,
},
{ initialNumItems: 2 },
(oldVal, newVal) => {
// Example differ function that calculates the difference between old and new data.
return newVal.length - (oldVal?.length ?? 0);
}
);
const {
results: serverThreads,
status,
isLoading,
loadMore,
} = usePaginatedQuery(
api.user.getThreads,
{
districtsId: district.districtsId,
timeFrame: memoizedTimestamp,
filter: filter,
},
{ initialNumItems: 2 }
);
const {
results: serverThreads,
status,
isLoading,
loadMore,
} = usePaginatedQuery(
api.user.getThreads,
{
districtsId: district.districtsId,
timeFrame: memoizedTimestamp,
filter: filter,
},
{ initialNumItems: 2 }
);
numItems
. Consider wrapping the field validator in v.optional(...)
if this is expected.
Path: .paginationOpts
I feel like im missing something really simple/obvious
oh my freaking goodness..
just reviewed docs again, and theres a part i always seem to skip..
i forgot to pass in an argument to loadMore([...])
my apologies@zid how did you solve this??
onClick={() => loadMore(1)}