Next.js中Firebase文档无法接收useRouter获取的动态ID路径问题
问题:Firebase无法通过useRouter动态获取的ID获取项目数据
我需要获取项目相关数据,但动态方式无法实现——Firebase不接受useRouter获取的ID。
动态获取ID的代码:
const router = useRouter(); const {projectID} = router.query; const getProject = doc(firestore, `projects/${projectID}`);
静态指定ID时可以正常工作:
const getProject = doc(firestore, `projects/CEtVpthX9zMqUsyYSjEQ`);
我尝试过以下操作:
- 对比路径字符串,控制台显示动态拼接的路径和静态路径完全相等,但动态方式依然无效
- 试过多种变量拼接方式:
const path = `projects/${projectID}`; const getProject = doc(firestore, path);const path = `${projectID}`; const getProject = doc(firestore, 'projects/' + path); - 使用react-router-dom的useParams获取ID,同样无法成功
测试代码如下:
const router = useRouter(); const {projectID} = router.query; const path = `projects/${projectID}`; const staticPath = `projects/CEtVpthX9zMqUsyYSjEQ` console.log(path); console.log(staticPath); console.log(path===staticPath); const getProject = doc(firestore, path);
动态路径输出结果与静态路径输出结果一致,但动态方式仍无法获取数据。
页面完整代码:
function Projects(): JSX.Element { const router = useRouter(); const {projectID} = router.query; const path = `${projectID}`; const getProject = doc(firestore, `projects/${projectID}`); const [isLoading, setIsLoading] = useState(false); const [project, setProject] = useState({}) useEffect(() => { const fetchProjectData = async () => { setIsLoading(true); const docSnap = await getDoc(getProject); if (docSnap.exists()){ const newProjectObj = { id: docSnap.id, ...docSnap.data() }; setProject(newProjectObj); setIsLoading(false); console.log(docSnap.data()); } else{ console.log("Нет данных") } } fetchProjectData(); return }, []); if(isLoading) return <div>Загрузка...</div>; return ( <> <div className={cn(styles.heading, styles.headBlock)}> {Object.keys(project) && Object.keys(project).length ? <div> <ProjectDetailed project={project}/> <CallBackBlock></CallBackBlock> </div> :null} </div> </> ); } export default withLayout(Projects);
问题原因分析
核心问题在于useEffect的依赖数组为空,且getProject的初始化时机不对:
- Next.js中
router.query在页面初始加载时可能为undefined(服务端渲染/静态生成阶段没有路由参数),此时projectID是undefined,getProject会变成projects/undefined,但后续projectID更新时,由于useEffect没有监听依赖,不会重新执行数据请求。 - 即使控制台后来显示
path和静态路径相等,那也是组件重新渲染后的结果,但此时useEffect已经执行过一次(用了错误的getProject),不会再触发。
解决方案
方案1:修复useEffect依赖与请求逻辑
function Projects(): JSX.Element { const router = useRouter(); const {projectID} = router.query; const [isLoading, setIsLoading] = useState(false); const [project, setProject] = useState({}) useEffect(() => { // 先判断projectID是否存在,避免无效请求 if (!projectID) return; const fetchProjectData = async () => { setIsLoading(true); // 在useEffect内部创建getProject,确保使用最新的projectID const getProject = doc(firestore, `projects/${projectID}`); const docSnap = await getDoc(getProject); if (docSnap.exists()){ const newProjectObj = { id: docSnap.id, ...docSnap.data() }; setProject(newProjectObj); console.log(docSnap.data()); } else{ console.log("Нет данных") } setIsLoading(false); } fetchProjectData(); }, [projectID]); // 监听projectID变化,参数更新时重新请求 if(isLoading) return <div>Загрузка...</div>; // 处理project为空的情况 if (!Object.keys(project).length) return <div>未找到项目数据</div>; return ( <> <div className={cn(styles.heading, styles.headBlock)}> <div> <ProjectDetailed project={project}/> <CallBackBlock></CallBackBlock> </div> </div> </> ); } export default withLayout(Projects);
方案2:使用useParams(如果是react-router-dom环境)
如果你的项目用的是react-router-dom而非Next.js的router,调整为:
import { useParams } from 'react-router-dom'; function Projects(): JSX.Element { const { projectID } = useParams<{ projectID: string }>(); const [isLoading, setIsLoading] = useState(false); const [project, setProject] = useState({}) useEffect(() => { if (!projectID) return; const fetchProjectData = async () => { setIsLoading(true); const getProject = doc(firestore, `projects/${projectID}`); const docSnap = await getDoc(getProject); if (docSnap.exists()){ const newProjectObj = { id: docSnap.id, ...docSnap.data() }; setProject(newProjectObj); } else{ console.log("Нет данных") } setIsLoading(false); } fetchProjectData(); }, [projectID]); // ... 其余渲染逻辑不变 }
关键注意点
- 永远不要在组件顶层依赖可能变化的路由参数创建Firebase引用,因为组件初始化时参数可能未就绪
- 必须将路由参数加入useEffect的依赖数组,确保参数更新时重新发起请求
- 每次请求前先校验参数是否存在,避免发送无效请求
内容的提问来源于stack exchange,提问作者Ilnaz Niyazov
相关产品推荐
相关产品推荐

