React.js/Next.js报错:TypeError: object null is not iterable问题求助
动态菜单实现中的TypeError问题解决
问题概述
在实现动态菜单逻辑时,使用[...state_name, {}]语法更新状态时触发报错:
TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
通过自定义useFetch钩子的LIST方法能正常从JSON Server获取数据,但循环遍历数据更新menu、subMenu状态时触发上述错误。
错误原因分析
- 初始值与异步加载问题:
useFetch中data的初始值为null,组件首次渲染时直接遍历data会导致报错,且数据加载完成前就执行了遍历逻辑。 - 状态类型不一致:代码中执行了
setSubMenu(null),后续再使用[...subMenu]时,subMenu是null类型,无法进行数组展开操作。 - 错误的状态更新时机:直接在组件函数体中编写for循环,每次组件渲染都会重复执行循环,且React的状态更新是异步的,循环中依赖当前
subMenu、menu状态值更新会导致逻辑混乱,无法得到预期结果。
修复方案
关键改动点
- 使用
useEffect监听data的变化,仅当data存在且加载完成时才处理菜单数据 - 先将原始数据整理为结构化的菜单对象(包含子菜单),再一次性更新
menu状态,避免多次调用setState - 保持
subMenu的数组类型,处理新的主菜单时重置为空数组,不设置为null
修改后的代码
MenuCreator组件
import { useState, useEffect } from "react"; import useFetch from "../functions/useFetch"; const MenuCreator = () => { const [menu, setMenu] = useState([]); const { data, loading, error } = useFetch('LIST', '/menuler'); useEffect(() => { // 仅当数据加载完成且存在时处理 if (!loading && data) { const structuredMenu = []; // 先筛选所有主菜单 const mainMenus = data.filter(item => item.main_menu === null); mainMenus.forEach(mainItem => { // 找到当前主菜单对应的所有子菜单 const subMenus = data.filter(subItem => subItem.main_menu === mainItem.id); structuredMenu.push({ id: mainItem.id, menu_adi: mainItem.menu_adi, menu_icon: mainItem.menu_icon, menu_link: mainItem.menu_link, subMenus: subMenus.map(sub => ({ id: sub.id, menu_adi: sub.menu_adi, main_menu: sub.main_menu, menu_link: sub.menu_link })) }); }); setMenu(structuredMenu); } }, [data, loading]); console.log(menu); return { menu }; } export default MenuCreator;
useFetch钩子(补充完善)
import { useEffect, useState } from 'react'; import { URL } from '../config/conf'; const useFetch = (type, url = "/") => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { switch(type){ case "ADD": const addRes = await fetch(`${URL}${url}`, { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(data) }); if (!addRes.ok) throw new Error("Ekleme İşlemi Başarısız"); setData("success"); break; case "DELETE": const deleteRes = await fetch(`${URL}${url}`, { method: "DELETE" }); if (!deleteRes.ok) throw new Error("Silme İşlemi Başarısız"); setData("success"); break; case "UPDATE": // 补充UPDATE逻辑 break; case "LIST": const listRes = await fetch(`${URL}${url}`); if (!listRes.ok) throw new Error("Veriler Getirilirken Bir Hata Oluştu"); const listData = await listRes.json(); setData(listData); break; } } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchData(); }, [type, url]); return { data, loading, error }; } export default useFetch;
改动说明
- MenuCreator组件:
- 添加
useEffect监听data和loading,确保数据加载完成后再处理菜单结构 - 使用
filter和forEach替代嵌套for循环,逻辑更清晰,避免多次调用setState - 直接构建完整的结构化菜单数组,一次性更新
menu状态,避免异步状态更新的问题
- 添加
- useFetch钩子:
- 将所有请求逻辑移入
useEffect,避免组件渲染时重复执行请求 - 使用async/await优化异步代码可读性
- 统一处理请求错误,确保
loading状态在请求结束后更新
- 将所有请求逻辑移入
内容的提问来源于stack exchange,提问作者Samet ÖZTÜRK
相关产品推荐
相关产品推荐

