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
相关产品推荐
相关产品推荐

