为何将列表渲染在独立组件中Material UI Select不触发onChange?
Material UI Select 嵌套子组件时 onChange 不触发问题
问题背景
我编写了两个React组件:子组件TopicList负责渲染多选下拉的选项列表,父组件TopicFeedDropdownSelector使用Material UI的Select组件并嵌套TopicList。当列表逻辑拆到子组件时,Select的onChange完全无法触发,但把列表直接写在父组件里就正常工作。
原组件代码
子组件 TopicList
import React from 'react'; import './index.css'; import { LMFeedTopics } from '@likeminds.community/feed-js-beta'; import { Checkbox, MenuItem } from '@mui/material'; interface TopicListProps { list: LMFeedTopics[]; checkedList: string[]; } const TopicList = ({ list, checkedList }: TopicListProps) => { return ( <> {list.map((topic: LMFeedTopics) => { return ( <MenuItem key={topic.Id} value={topic.Id} role="option"> <div className="topicTile"> <Checkbox checked={checkedList.includes(topic.Id)} /> <span>{topic.name}</span> </div> </MenuItem> ); })} </> ); }; export default TopicList;
父组件 TopicFeedDropdownSelector
import React, { useCallback, useEffect, useState } from 'react'; import './index.css'; import { LMFeedTopics } from '@likeminds.community/feed-js-beta'; import { lmFeedClient } from '../../..'; import { Checkbox, FormControl, MenuItem, Select } from '@mui/material'; import TopicList from '../topic-list'; interface TopicFeedDropdownSelectorProps {} const TopicFeedDropdownSelector = () => { const [topicList, setTopicList] = useState<any[]>([]); const [checkedTopicList, setCheckedTopicList] = useState<string[]>([]); const [page, setPage] = useState<number>(1); const [searchKey, setSearchKey] = useState<string>(''); const getData = useCallback(async () => { // Used go get some data with an API call. }, [page, searchKey]); useEffect(() => { getData(); }, []); return ( <div> <Select multiple={true} IconComponent={() => ( <svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M6.52567 15.7821L11.287 11.0208C11.5775 10.7303 11.5775 10.2591 11.287 9.96863C10.9964 9.67807 10.5254 9.67807 10.2349 9.96863L6.74356 13.4599L6.74356 0.743959C6.74356 0.333115 6.41044 0 5.9996 0C5.58882 0 5.25564 0.333115 5.25564 0.743959L5.25564 13.4599L1.76433 9.96875C1.47377 9.67819 1.00275 9.67819 0.712193 9.96875C0.567032 10.114 0.494303 10.3044 0.494303 10.4948C0.494303 10.6852 0.567032 10.8756 0.712193 11.0209L5.47353 15.7821C5.76409 16.0727 6.23511 16.0727 6.52567 15.7821Z" fill="#666666" /> </svg> )} value={checkedTopicList} onChange={(e: any) => { console.log(e); }}> <TopicList list={topicList} checkedList={checkedTopicList} /> </Select> </div> ); }; export default TopicFeedDropdownSelector;
原因分析
Material UI的Select组件内部通过遍历直接子节点来识别可交互的MenuItem选项,并绑定对应的点击、键盘导航等事件。当你把MenuItem包裹在自定义组件TopicList中时,Select只会识别到TopicList这个自定义组件节点,不会递归处理其内部的MenuItem,导致这些选项无法被注册到Select的交互逻辑中,自然触发不了onChange。
解决方案
方案1:让Select直接获取MenuItem节点(推荐)
修改父组件中TopicList的调用方式,直接执行组件函数返回MenuItem数组,而非将其作为React组件渲染,这样Select就能直接拿到MenuItem节点:
// 父组件中修改Select的children部分 <Select multiple={true} IconComponent={() => (/* 原有图标代码 */)} value={checkedTopicList} onChange={(e: any) => { console.log(e); // 更新选中状态 setCheckedTopicList(e.target.value); }}> {/* 直接调用TopicList函数返回MenuItem数组 */} {TopicList({ list: topicList, checkedList: checkedTopicList })} </Select>
同时可以简化TopicList组件,去掉外层的Fragment(可选,数组返回本身不需要包裹):
const TopicList = ({ list, checkedList }: TopicListProps) => { return list.map((topic: LMFeedTopics) => { return ( <MenuItem key={topic.Id} value={topic.Id} role="option"> <div className="topicTile"> <Checkbox checked={checkedList.includes(topic.Id)} /> <span>{topic.name}</span> </div> </MenuItem> ); }); };
方案2:手动传递交互逻辑(不推荐)
如果一定要保留组件嵌套的形式,可以手动将Select的状态更新逻辑传递给子组件,让子组件处理选项的点击事件:
- 修改
TopicList的Props,添加点击回调:
interface TopicListProps { list: LMFeedTopics[]; checkedList: string[]; onToggleTopic: (topicId: string) => void; } const TopicList = ({ list, checkedList, onToggleTopic }: TopicListProps) => { return ( <> {list.map((topic: LMFeedTopics) => { const isChecked = checkedList.includes(topic.Id); return ( <MenuItem key={topic.Id} value={topic.Id} role="option" onClick={() => onToggleTopic(topic.Id)} > <div className="topicTile"> <Checkbox checked={isChecked} /> <span>{topic.name}</span> </div> </MenuItem> ); })} </> ); };
- 父组件中传递回调函数并更新状态:
<Select multiple={true} IconComponent={() => (/* 原有图标代码 */)} value={checkedTopicList} onChange={(e) => setCheckedTopicList(e.target.value)}> <TopicList list={topicList} checkedList={checkedTopicList} onToggleTopic={(topicId) => { setCheckedTopicList(prev => { return prev.includes(topicId) ? prev.filter(id => id !== topicId) : [...prev, topicId]; }); }} /> </Select>
注意:这种方式会丢失
Select内置的键盘导航、批量选择等功能,仅适合简单场景。
内容的提问来源于stack exchange,提问作者Gaurav Rajput
相关产品推荐
相关产品推荐

