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

在使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:03:10