T3栈项目收藏功能报错:无法解构undefined的question.id
问题:T3栈开发Web应用收藏功能报错
TypeError: Cannot destructure property 'id' of 'question' as it is undefined 报错详情
TypeError: Cannot destructure property 'id' of 'question' as it is undefined. input: RouterInputs["questions"]["getQuestions"]; 11 | }) { > 12 | const { id } = question; | ^ 13 | 14 | return ( 15 | <>
该功能在拆分FeedQuestion与QuestionCard组件前可正常运行,给question判空并赋值空对象后,错误会转移到下一个组件。
相关组件代码
FeedQuestion组件(报错位置)
function FeedQuestion({ question, client, loggedIn, input }: { question: RouterOutputs["questions"]["getQuestions"]["items"][0]; client: QueryClient; loggedIn: boolean; input: RouterInputs["questions"]["getQuestions"]; }) { const { id } = question; return (<> <Link href={`/debate/${id}`}> <QuestionCard question={question} client={client} loggedIn={loggedIn} input={input} /> <div className="block max-w-sm p-4 bg-white rounded-lg shadow-inner" > {"Most popular argument"} </div> </Link> </>)} export default FeedQuestion;
QuestionsFeed组件(问题列表展示)
function QuestionsFeed() { const { data: session } = useSession(); const scrollPosition = useScrollPosition(); const searchParams = useSearchParams(); const hotFilter = searchParams.get('hot'); const typeFilter = searchParams.get('type'); let where = {}, orderBy = {}; if (hotFilter) { const now = new Date(); where = { createdAt: { gte: new Date(now.getFullYear(), now.getMonth() - 1, now.getDay()), }, }; orderBy = { arguments: { _count: Prisma.SortOrder.desc, } }; } else if (typeFilter) { where = { type: typeFilter as Type, }; orderBy = { id: Prisma.SortOrder.desc, }; } else { orderBy = { id: Prisma.SortOrder.desc, }; } const input = { where, orderBy, } const { data, hasNextPage, fetchNextPage, isFetching } = api.questions.getQuestions.useInfiniteQuery( input, { getNextPageParam: (lastPage) => lastPage.nextCursor, }, ); const client = useQueryClient(); const questions = data?.pages.flatMap((page) => page.items) ?? []; useEffect(() => { if (scrollPosition >= 90 && hasNextPage && !isFetching) { fetchNextPage(); } }, [scrollPosition, hasNextPage, isFetching, fetchNextPage]); return ( <div> <ul className="flex flex-col gap-10 pt-24 pb-4"> { isFetching ? pulseLoadingCardAnimation() : questions?.map((q, index) => { return ( <li key={index}> <FeedQuestion question={q} client={client} loggedIn={session ? true : false} input={input} /> </li> ); }) } </ul> {!hasNextPage && <p className="text-neutral-400 pb-4">These are all of our questions for now!</p>} </div> ) }
QuestionCard组件
function QuestionCard({ question, client, loggedIn, input }: { question: RouterOutputs["questions"]["getQuestions"]["items"][0], client: QueryClient; loggedIn: boolean; input: RouterInputs["questions"]["getQuestions"]; }) { const { title, id, favoritedBy } = question; const favoriteQuestion = api.questions.favorite.useMutation({ onSuccess: (data, variables) => updateCache({ client, data, variables, action: "favorite", input }), }).mutateAsync; const unfavoriteQuestion = api.questions.unfavorite.useMutation( { onSuccess: (data, variables) => updateCache({ client, data, variables, action: "unfavorite", input }), } ).mutateAsync; const hasFavorited = favoritedBy.length > 0; return ( <> <div className="block max-w-lg p-6 bg-white rounded-lg shadow" > <div className="flex justify-end"> <AiFillHeart color={loggedIn ? hasFavorited ? "purple" : "gray" : "lightgray"} size="1.5rem" onClick={(e) => { e.preventDefault(); e.stopPropagation(); if (!loggedIn) { return; } if (hasFavorited) { unfavoriteQuestion({ questionId: id, }); return; } favoriteQuestion({ questionId: id, }); }} /> </div> <div className=""> <h5 className="mb-6 text-4xl font-bold tracking-tight text-black">{title}</h5> </div> </div> </> ); }
问题分析
- 类型标注与实际值不符:虽然TypeScript标注
question是RouterOutputs的item类型,但运行时可能存在undefined值,比如分页返回的items数组中存在空项,或者flatMap处理后产生undefined。 - 组件拆分后暴露问题:拆分前组件内可能隐式处理了空值,拆分后父组件传递的
q直接进入子组件,空值问题被触发。 - 判空后错误转移:说明
question确实存在undefined的情况,只是之前未暴露,错误转移到下一个组件是因为下一个组件同样直接解构question。
解决方案
- 过滤空值项:在
QuestionsFeed组件的map前过滤掉undefined的问题项,避免传递空值给子组件:questions?.filter(Boolean).map((q) => { return ( <li key={q.id}> {/* 改用question.id作为key,比index更可靠 */} <FeedQuestion question={q} client={client} loggedIn={session ? true : false} input={input} /> </li> ); }) - 子组件添加运行时检查:在
FeedQuestion和QuestionCard组件中添加空值检查,防止解构报错:// FeedQuestion组件 function FeedQuestion({ question, client, loggedIn, input }) { if (!question) return null; // 或返回加载占位符 const { id } = question; // 其余代码 } // QuestionCard组件 function QuestionCard({ question, client, loggedIn, input }) { if (!question) return null; const { title, id, favoritedBy } = question; // 其余代码 } - 检查后端接口返回:确认
tRPC的getQuestions接口返回的items数组中没有null或undefined,可能是后端查询或序列化过程中出现异常。 - 优化分页逻辑:检查
getNextPageParam的处理,确保分页返回的nextCursor和items正确,避免加载到无效数据。
内容的提问来源于stack exchange,提问作者ClarenceHD
相关产品推荐
相关产品推荐

