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

React中将数据库查询结果传入客户端模态框的Async/await问题

问题分析

我有两个获取数据的Hook:一个从预填充列表获取,运行正常;另一个从数据库查询,但无法正常工作。

正常工作的Hook代码

import { courseList } from "../components/Course";

const formattedCourses = courseList.map((course) => ({
    label:course.label,
    abbr:course.abbr,
}));

const useCourses = () => {
    const getAll = () => formattedCourses;
    const getByAbbr = (value:string) => {
        return formattedCourses.find((item)=>item.abbr===value);
    }
    return {
        getAll,
        getByAbbr
    }
};

export default useCourses;

有问题的数据库查询Hook代码

import getCourses, { ICourseListingParams } from "../actions/getCourses";

const searchParams : ICourseListingParams={};

const useDefinedCourses = async () => {
    
    const courseList = await getCourses(searchParams);
    const formattedCourses = courseList.map((course) => ({
        label:course.type+' Year '+course.year,
        abbr:course.type,
    }));
    const getAll = () => formattedCourses;
    const getByAbbr = (value:string) => {
        return formattedCourses.find((item)=>item.abbr===value);
    }
    return {
        getAll,
        getByAbbr
    }
};

export default useDefinedCourses;

使用时的问题

在函数组件中使用useCourses一切正常,但使用useDefinedCourses时必须加await,进而要把组件改成异步函数:

const DefinedCourseSelect:React.FC<CourseSelectProps> =  async ({
    value,
    onChange
}) => {
    const { getAll} = await useDefinedCourses();  
}

将这个组件放在标记了'use client'的模态框中时,出现"Can't resolve fs"错误。尝试过移除'use client'但后续需要用useState,不可行;直接解析Promise也报错。


解决方案

React不允许自定义Hook是异步函数,也不能把组件改成异步函数(这会导致组件返回Promise而非React元素)。同时getCourses如果是服务端数据库操作,直接在客户端调用会触发fs相关错误(因为服务端依赖在客户端不存在)。按以下方式修改:

1. 修正自定义Hook的异步逻辑

用useState和useEffect处理异步数据获取,在Hook内部管理加载状态和数据:

import { useState, useEffect } from 'react';
import getCourses, { ICourseListingParams } from "../actions/getCourses";

const useDefinedCourses = () => {
    const [formattedCourses, setFormattedCourses] = useState<{label: string; abbr: string}[]>([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    useEffect(() => {
        const fetchCourses = async () => {
            try {
                const searchParams: ICourseListingParams = {};
                const courseList = await getCourses(searchParams);
                const formatted = courseList.map((course) => ({
                    label: `${course.type} Year ${course.year}`,
                    abbr: course.type,
                }));
                setFormattedCourses(formatted);
            } catch (err) {
                setError(err as Error);
            } finally {
                setLoading(false);
            }
        };

        fetchCourses();
    }, []);

    const getAll = () => formattedCourses;
    const getByAbbr = (value: string) => {
        return formattedCourses.find((item) => item.abbr === value);
    };

    return {
        getAll,
        getByAbbr,
        loading,
        error
    };
};

export default useDefinedCourses;

2. 确保getCourses是客户端兼容的API调用

如果getCourses直接调用数据库,需要把它改成API路由调用,让客户端通过HTTP请求获取数据,避免直接在客户端执行服务端代码:

  • 创建app/api/courses/route.ts(Next.js 13+ App Router):
import { NextResponse } from 'next/server';
import getCourses from '@/lib/server-side-db-call'; // 这里是真正的数据库查询逻辑

export async function GET(request: Request) {
    try {
        const courses = await getCourses({});
        return NextResponse.json(courses);
    } catch (err) {
        return NextResponse.json({ error: 'Failed to fetch courses' }, { status: 500 });
    }
}
  • 修改getCourses为客户端API调用:
import { ICourseListingParams } from "./types"; // 共用类型定义

export default async function getCourses(params: ICourseListingParams) {
    const res = await fetch('/api/courses', {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
        },
        // 如需传递参数,可转为查询参数或请求体
        // body: JSON.stringify(params)
    });

    if (!res.ok) {
        throw new Error('Failed to fetch courses');
    }

    return res.json();
}

3. 在客户端组件中使用修正后的Hook

组件无需改成异步,直接使用Hook并处理加载/错误状态:

'use client';
import { useState } from 'react';
import useDefinedCourses from './useDefinedCourses';

const DefinedCourseSelect: React.FC<CourseSelectProps> = ({ value, onChange }) => {
    const { getAll, loading, error } = useDefinedCourses();
    const [localValue, setLocalValue] = useState(value);

    if (loading) {
        return <div>Loading courses...</div>;
    }

    if (error) {
        return <div>Error loading courses: {error.message}</div>;
    }

    const courses = getAll();

    return (
        <select 
            value={localValue} 
            onChange={(e) => {
                setLocalValue(e.target.value);
                onChange(e.target.value);
            }}
        >
            {courses.map(course => (
                <option key={course.abbr} value={course.abbr}>
                    {course.label}
                </option>
            ))}
        </select>
    );
};

export default DefinedCourseSelect;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 07:25:38