在使用Chakra UI的MERN项目中引入Material UI Badge组件报错
解决Chakra UI项目中混用Material UI Badge组件的兼容性错误
问题描述
在基于MERN栈且采用Chakra UI的项目中,尝试使用Material UI的Badge组件实现通知计数徽章功能时,触发以下错误:
TypeError: Cannot read properties of undefined (reading 'fontFamily')
完整错误调用栈
Uncaught TypeError: Cannot read properties of undefined (reading 'fontFamily') at @mui_material_Badge.js?v=279a16fe:15880:34 at muiStyledFunctionResolver (@mui_material_Badge.js?v=279a16fe: 13556:26) at transformedStyleArg (@mui_material_Badge.js?v=279a16fe: 13672:42) at handleInterpolation (chunk-SXLILSWM.js?v=6d6d68ca:1386:22) at serializeStyles2 (chunk-SXLILSWM.js?v=6d6d68ca:1484:15) at chunk-SXLILSWM.js?v=6d6d68ca:2105:24 at chunk-SXLILSWM.js?v=6d6d68ca:1555:12 at renderWithHooks (chunk-GSZ7ISAW.js?v=6d6d68ca:12171:26) at updateForwardRef (chunk-GSZ7ISAW.js?v=6d6d68ca:14327:28) at beginWork (chunk-GSZ7ISAW.js?v=6d6d68ca:15934:22)
错误发生在<MuiBadgeBadge>组件中,调用链涉及ChakraComponent、SideDrawer等组件。
相关组件代码(SideDrawer.js)
import React, { useState } from 'react' import { Box, Button, Tooltip, Text, Menu, MenuButton, MenuList, CheckboxGroup, Avatar, MenuItem, Drawer, useDisclosure, DrawerOverlay, DrawerContent, DrawerHeader, DrawerBody, Input, useToast, Spinner } from '@chakra-ui/react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import Badge from '@mui/material/Badge'; import { BellIcon, ChevronDownIcon } from '@chakra-ui/icons' import { ChatState } from '../../Context/ChatProvider'; import ProfileModal from './ProfileModal'; import ChatLoading from '../ChatLoading'; import UserListItem from '../UserAvatar/UserListItem'; import { getSender } from '../../config/ChatLogics'; function SideDrawer() { const [search, setSearch] = useState(""); const [searchResult, setSearchResult] = useState([]); const [loading, setLoading] = useState(false); const [loadingChat, setLoadingChat] = useState(); const { user, setSelectedChat, chats, setChats, notification, setNotification } = ChatState(); const navigate = useNavigate(); const { isOpen, onOpen, onClose } = useDisclosure(); const toast = useToast(); const logoutHandler = () => { localStorage.removeItem("userInfo"); navigate('/'); } const handleSearch = async () => { if (!search) { toast({ title: "Please enter something in search", status: "warning", duration: 5000, isclosable: true, position: "top-left" }); return; } try { setLoading(true); const config = { headers: { Authorization: `Bearer ${user.token}`, }, }; const { data } = await axios.get(`/api/user?search=${search}`, config); setLoading(false); setSearchResult(data); } catch (error) { toast({ title: "Error occured!", description: "Failed to load the search results", status: "error", duration: 5000, isclosable: true, position: "bottom-left" }); } } const accessChat = async (userId) => { try { setLoadingChat(true); const config = { headers: { "Content-type": "application/json", Authorization: `Bearer ${user.token}`, } }; const { data } = await axios.post('/api/chat', { userId }, config); if (!chats.find((c) => c._id === data._id)) setChats([data], ...chats); setSelectedChat(data) setLoadingChat(false) onClose(); } catch (error) { toast({ title: "Error fetching chat!", description: error.message, status: "error", duration: 5000, isclosable: true, position: "bottom-left" }); } } const handleClose = () => { setSearch(""); setSearchResult([]) onClose(); } return ( <> <Box display="flex" justifyContent='space-between' alignContent='center' bg='white' w='100%' p='5px 10px' borderWidth='5px' > <Tooltip label="Search Users to chat" hasArrow placement="bottom-end"> <Button variant='ghost' borderWidth='1px' onClick={onOpen}> <i className="fa fa-search" aria-hidden="true"></i> <Text display={{ base: "none", md: "flex" }} px='4'>Search User</Text> </Button> </Tooltip> <Text fontSize='2xl' fontFamily='work-sans'>The Chat App</Text> <div> <Menu> <MenuButton p={1}> {/* 此处使用了Material UI的Badge组件 */} <Badge badgeContent={notification.length} color="primary"> <BellIcon fontSize="2xl" m={1} /> </Badge> <MenuList> {!notification.length && "No new Messages"} {notification.map((noti) => ( <MenuItem key={noti._id} onClick={() => { setSelectedChat(noti.chat); setNotification(notification.filter((n) => n !== noti)) }} > {noti.chat.isGroupChat ? `New Message in ${noti.chat.chatName}` : `New Message from ${getSender(user, noti.chat.users)}`} </MenuItem> ))} </MenuList> </MenuButton> </Menu> <Menu> <MenuButton as={Button} rightIcon={<ChevronDownIcon />}> <Avatar size='sm' cursor='pointer' name={user.name} src={user.pic} /> </MenuButton> <MenuList> <ProfileModal user={user}> <MenuItem>My Profile</MenuItem> </ProfileModal> <MenuItem onClick={logoutHandler}>Logout</MenuItem> </MenuList> </Menu> </div> </Box> <Drawer placement='left' onClose={() => handleClose()} isOpen={isOpen}> <DrawerOverlay /> <DrawerContent> <DrawerHeader borderBottomWidth="1px">Search Users</DrawerHeader> <DrawerBody> <Box display='flex' pb={2}> <Input placeholder="Search by name or email" mr={2} value={search} onChange={(e) => setSearch(e.target.value)} /> <Button onClick={handleSearch}>Go</Button> </Box> {loading ? (<ChatLoading />) : ( searchResult?.map(user => <UserListItem key={user._id} user={user} handleFunction={() => accessChat(user._id)} />) )} {loadingChat && <Spinner ml="auto" display="flex" />} </DrawerBody> </DrawerContent> </Drawer> </> ) } export default SideDrawer
解决方案
1. 优先使用Chakra UI原生Badge组件
Chakra UI本身提供了Badge组件,无需引入Material UI即可实现通知计数功能,从根源避免样式系统冲突(Chakra和MUI的样式引擎底层实现不同,混用会导致样式上下文缺失,触发fontFamily读取错误)。
修改步骤:
- 移除Material UI Badge的导入:
import Badge from '@mui/material/Badge' - 从Chakra UI导入Badge:
import { ..., Badge } from '@chakra-ui/react' - 调整组件用法,用相对定位实现徽章叠加效果:
<MenuButton p={1}> <Box position="relative"> <BellIcon fontSize="2xl" m={1} /> {notification.length > 0 && ( <Badge colorScheme="red" variant="solid" fontSize="0.6rem" position="absolute" top={0} right={0} borderRadius="full" px={1} minW="1.25rem" textAlign="center" > {notification.length} </Badge> )} </Box> <MenuList> {/* 原有菜单列表内容 */} </MenuList> </MenuButton>
2. 若必须混用MUI组件的兼容方案
如果业务需求必须使用Material UI组件,需要为MUI提供独立的样式上下文,避免和Chakra的样式系统冲突:
- 确保安装兼容版本的依赖:
npm install @mui/material @emotion/react @emotion/styled - 在应用顶层为MUI组件包裹
ThemeProvider:
import { ChakraProvider } from '@chakra-ui/react'; import { ThemeProvider, createTheme } from '@mui/material/styles'; const muiTheme = createTheme(); function App() { return ( <ChakraProvider> <ThemeProvider theme={muiTheme}> {/* 应用所有内容 */} </ThemeProvider> </ChakraProvider> ); }
注意:这种方式仍可能存在样式冲突风险,仅作为备选方案。
3. 添加错误边界增强应用稳定性
为SideDrawer组件添加错误边界,避免单个组件错误导致整个应用崩溃:
import React from 'react'; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('组件错误捕获:', error, errorInfo); } render() { if (this.state.hasError) { return <Box p={4} textAlign="center">加载失败,请刷新重试</Box>; } return this.props.children; } } // 使用时包裹SideDrawer <ErrorBoundary> <SideDrawer /> </ErrorBoundary>
内容的提问来源于stack exchange,提问作者Naveen Kumar
相关产品推荐
相关产品推荐

