You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
        </>
    );
}

问题分析

  1. 类型标注与实际值不符:虽然TypeScript标注question是RouterOutputs的item类型,但运行时可能存在undefined值,比如分页返回的items数组中存在空项,或者flatMap处理后产生undefined。
  2. 组件拆分后暴露问题:拆分前组件内可能隐式处理了空值,拆分后父组件传递的q直接进入子组件,空值问题被触发。
  3. 判空后错误转移:说明question确实存在undefined的情况,只是之前未暴露,错误转移到下一个组件是因为下一个组件同样直接解构question。

解决方案

  1. 过滤空值项:在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>
        );
    })
    
  2. 子组件添加运行时检查:在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;
        // 其余代码
    }
    
  3. 检查后端接口返回:确认tRPC的getQuestions接口返回的items数组中没有null或undefined,可能是后端查询或序列化过程中出现异常。
  4. 优化分页逻辑:检查getNextPageParam的处理,确保分页返回的nextCursor和items正确,避免加载到无效数据。

内容的提问来源于stack exchange,提问作者ClarenceHD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 01:57:57