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

React下拉菜单开发问题:数据库嵌套数组映射报错

问题描述

我想实现如下样式的下拉菜单:

  • title 1
    |-> 带链接的子项列表
  • title 2
    |-> 带链接的子项列表
  • title 3
    |-> 带链接的子项列表

现有代码如下:

footerMenu.js

import { useState } from "react";
import FooterItem from "./FooterItem";

const FooterMenu = (props) => {
  return (
    <>
      <div className="dropdown-menu shadow-lg shadow-slate-300 ml-[40vw] px-12">
        <ul className="list-none">
          <FooterItem val="About Us" />
          <FooterItem val="Careers" />
          <FooterItem val="Social Impact" />
          <FooterItem val="For Business Partners" />
          <FooterItem val="Order and Pickup" />
        </ul>
      </div>
    </>
  );
};

export default FooterMenu;

footerItem.js

import { useState } from "react";
import { FooterList } from "./FooterList";

const FooterItem = (props) => {
  const [open, setOpen] = useState(false);

  const showDropdown = () => {
    setOpen(!open);
  };

  return (
    <li onClick={showDropdown} className="menu-head p-5">
      <h2 className="text-xl font-semibold mb-5">{props.val}</h2>
      <ul
        className={`menu-item relative px-1 gap-y-5 ${
          open ? "open-list" : "close-list"
        }`}
      >
        <FooterList />
      </ul>
    </li>
  );
};
export default FooterItem;

footerList.js

import axios from "axios";

export const FooterList = async () => {
  let response = await axios.get(`http://localhost:5000/footerMenu`);
  return response.data[0].content.map((val) => {
    return (
      <>
        <li>
          <a href={val.link}>
            {val.subtitle}
          </a>
        </li>
      </>
    );
  });

我的思路是给每个菜单标题在数据库中配置对应子项列表,通过axios拉取数据,但目前出现报错:objects are not valid as a react child(found: Object Promise])。数据库结构为:表中包含title和content字段,content是数组类型,每个元素包含subtitle(子项标题)和link(跳转链接)。同时想了解有没有更简便的下拉菜单实现方式。


解决方案

一、修复Promise报错问题

报错核心原因是FooterList是异步函数,直接作为React组件使用时返回的是Promise对象,而React无法渲染Promise。需要调整数据请求逻辑,改用React的useEffect和useState处理异步数据:

方案1:将请求逻辑整合到FooterItem组件

// 修改后的footerItem.js
import { useState, useEffect } from "react";
import axios from "axios";

const FooterItem = (props) => {
  const [open, setOpen] = useState(false);
  const [menuItems, setMenuItems] = useState([]);

  const showDropdown = () => {
    setOpen(!open);
  };

  useEffect(() => {
    const fetchMenuItems = async () => {
      try {
        // 根据标题筛选对应子项(假设接口支持通过title参数查询)
        const response = await axios.get(`http://localhost:5000/footerMenu?title=${props.val}`);
        setMenuItems(response.data[0].content);
      } catch (err) {
        console.error("获取菜单数据失败:", err);
      }
    };

    // 可选优化:仅在下拉展开时请求数据,减少不必要请求
    if (open) {
      fetchMenuItems();
    }
  }, [open, props.val]);

  return (
    <li onClick={showDropdown} className="menu-head p-5">
      <h2 className="text-xl font-semibold mb-5">{props.val}</h2>
      <ul
        className={`menu-item relative px-1 gap-y-5 ${
          open ? "open-list" : "close-list"
        }`}
      >
        {menuItems.map((item, index) => (
          <li key={index}>
            <a href={item.link}>{item.subtitle}</a>
          </li>
        ))}
      </ul>
    </li>
  );
};
export default FooterItem;

方案2:保留FooterList组件,改为受控组件

// 修改后的footerList.js
export const FooterList = ({ items }) => {
  return items.map((item, index) => (
    <li key={index}>
      <a href={item.link}>{item.subtitle}</a>
    </li>
  ));
};
// 对应修改后的footerItem.js
import { useState, useEffect } from "react";
import { FooterList } from "./FooterList";
import axios from "axios";

const FooterItem = (props) => {
  const [open, setOpen] = useState(false);
  const [menuItems, setMenuItems] = useState([]);

  const showDropdown = () => {
    setOpen(!open);
  };

  useEffect(() => {
    const fetchMenuItems = async () => {
      const response = await axios.get(`http://localhost:5000/footerMenu?title=${props.val}`);
      setMenuItems(response.data[0].content);
    };
    if (open) fetchMenuItems();
  }, [open, props.val]);

  return (
    <li onClick={showDropdown} className="menu-head p-5">
      <h2 className="text-xl font-semibold mb-5">{props.val}</h2>
      <ul
        className={`menu-item relative px-1 gap-y-5 ${
          open ? "open-list" : "close-list"
        }`}
      >
        <FooterList items={menuItems} />
      </ul>
    </li>
  );
};
export default FooterItem;

关键注意点:

  • React组件不能是异步函数,异步数据请求必须通过useEffect配合useState处理
  • 列表项必须添加key属性,避免React渲染警告

二、更简便的下拉菜单实现方式

1. CSS原生实现(无需JS控制状态)

适合简单场景,利用CSS:hover伪类实现鼠标悬停展开:

.menu-head {
  position: relative;
}
.menu-item {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background-color: #fff;
  padding: 8px 0;
}
.menu-head:hover .menu-item {
  display: block;
}

这种方式可以直接去掉JS中的open状态管理,大幅简化代码。

2. 使用成熟UI组件库

如果项目允许,直接用现成的UI组件库可以快速实现功能,比如:

  • Tailwind UI:适配Tailwind CSS的预制组件,有现成下拉菜单模板
  • Ant Design:Dropdown组件支持多种触发方式(点击/悬停),功能完善
  • React Bootstrap:Dropdown组件开箱即用,风格统一

以Ant Design为例,实现代码非常简洁:

import { Dropdown, Menu } from 'antd';

const FooterMenu = () => {
  // 可通过接口请求动态生成items数组
  const aboutMenuItems = [
    { key: '1', label: <a href="/about">公司介绍</a> },
    { key: '2', label: <a href="/contact">联系我们</a> },
  ];

  return (
    <Dropdown overlay={<Menu items={aboutMenuItems} />}>
      <a onClick={(e) => e.preventDefault()}>About Us</a>
    </Dropdown>
  );
};

3. 简化自定义逻辑

如果坚持自己实现,可以将菜单数据请求逻辑提升到FooterMenu组件,一次性拉取所有菜单数据后分发给各个FooterItem,避免重复请求接口;或者用React Context统一管理菜单状态,减少组件间的重复代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 00:55:34