React多组件Snackbar重叠问题:如何实现堆叠显示?
问题:多组件Snackbar重叠,如何实现堆叠显示?
在dashboard.js中整合了多个自定义组件,每个组件都设置了超时时间为3秒的Snackbar。加载或刷新仪表盘页面时,这些Snackbar出现重叠现象,调整位置后问题仍未解决,需要实现它们的堆叠显示。
提供的代码
dashboard.js
import React from 'react'; import { Grid, IconButton, Typography } from '@mui/material'; import Cards from './cards/Cards'; import ServerStatus from './ServerStatus'; import ServerGroupStatus from './ServerGroupStatus'; import DatabaseStatus from './DatabaseStatus'; import WebServiceStatus from './WebserviceStatus'; export default function Dashboard() { return ( <Grid container spacing={2}> <Grid item xs={12}> <Cards /> </Grid> <Grid item xs={12} style={{ marginBottom: '16px' }}> </Grid> <ServerStatus /> <Grid item xs={12} style={{ marginBottom: '16px' }}> </Grid> <ServerGroupStatus /> <Grid item xs={12} style={{ marginBottom: '16px' }}> </Grid> <DatabaseStatus /> <Grid item xs={12} style={{ marginBottom: '16px' }}> </Grid> <WebServiceStatus /> </Grid> ); }
组件示例(ServerStatus)
import React, { useState, useEffect } from 'react'; import { Grid, Card, CardHeader, CardContent, Table, TableHead, TableRow, TableCell, TableBody, Button, Snackbar, Alert, Skeleton } from '@mui/material'; import { Refresh as RefreshIcon, ZoomIn as ZoomInIcon } from '@mui/icons-material'; import { getServers } from "../api/server-service"; import { useTheme } from '@mui/material/styles'; function ServerStatus() { const theme = useTheme(); const [loading, setLoading] = useState(true); const [serverData, setServerData] = useState([]); const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); const [snackbarSeverity, setSnackbarSeverity] = useState("success"); console.log(theme.palette.primary.main); useEffect(() => { fetchServers(); }, []); const handleRefresh = () => { setLoading(true); fetchServers(); }; const fetchServers = () => { getServers() .then((response) => { console.log("Server data:", response.data); setTimeout(() => { setServerData(response.data); setLoading(false); if (response.data.length > 0) { showSnackbar("Successfully fetched all Servers.", "success"); } }, 1000); }) .catch((error) => { console.log(error); showSnackbar("Error fetching servers: " + error.message, "error"); }); }; const showSnackbar = (message, severity) => { setSnackbarMessage(message); setSnackbarSeverity(severity); setSnackbarOpen(true); }; const handleCloseSnackbar = () => { setSnackbarOpen(false); }; const handleZoomInClick = () => { console.log('Zoom in clicked'); }; return ( <Grid item xs={12}> <Card elevation={3}> <CardHeader title="Server Status" action={ <Button variant="contained" color="secondary" startIcon={<RefreshIcon />} onClick={handleRefresh} > Refresh </Button> } /> <CardContent> <Table> <TableHead> <TableRow> <TableCell>Server Name</TableCell> <TableCell>Host</TableCell> <TableCell>Active Status</TableCell> <TableCell></TableCell> </TableRow> </TableHead> <TableBody> {loading ? ( <TableRow> <TableCell colSpan={4}> <Skeleton animation="wave" /> <Skeleton animation="wave" /> <Skeleton animation="wave" /> <Skeleton animation="wave" /> </TableCell> </TableRow> ) : serverData.length === 0 ? ( <TableRow> <TableCell colSpan={4}>Add Monitor Server Request To check status...</TableCell> </TableRow> ) : ( serverData.map((server) => ( <TableRow key={server.serverName}> <TableCell>{server.name}</TableCell> <TableCell>{server.host}</TableCell> <TableCell> <Button variant="contained" style={{ backgroundColor: server.isActive ? theme.palette.success.main : theme.palette.error.main, color: 'white', }} disabled={!server.isActive} > {server.isActive ? "Active" : "InActive"} </Button> </TableCell> <TableCell> <ZoomInIcon onClick={handleZoomInClick} style={{ cursor: 'pointer' }} /> </TableCell> </TableRow> )) )} </TableBody> </Table> </CardContent> </Card> <Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'right' }} open={snackbarOpen} autoHideDuration={3000} onClose={handleCloseSnackbar} > <Alert onClose={handleCloseSnackbar} severity={snackbarSeverity}> {snackbarMessage} </Alert> </Snackbar> </Grid> ); } export default ServerStatus;
解决方案
问题根源是每个组件独立渲染自己的Snackbar,且所有Snackbar使用相同的锚点位置,导致重叠。以下两种方法可以解决:
方法1:使用MUI SnackbarProvider统一管理(推荐)
MUI提供的SnackbarProvider组件可以全局管理所有Snackbar的显示,自动处理堆叠逻辑,无需手动调整位置。
- 修改Dashboard.js,用
SnackbarProvider包裹所有组件:
import React from 'react'; import { Grid } from '@mui/material'; import { SnackbarProvider } from '@mui/material/Snackbar'; import Cards from './cards/Cards'; import ServerStatus from './ServerStatus'; import ServerGroupStatus from './ServerGroupStatus'; import DatabaseStatus from './DatabaseStatus'; import WebServiceStatus from './WebserviceStatus'; export default function Dashboard() { return ( <SnackbarProvider anchorOrigin={{ vertical: 'top', horizontal: 'right' }} autoHideDuration={3000} > <Grid container spacing={2}> <Grid item xs={12}> <Cards /> </Grid> <Grid item xs={12} style={{ marginBottom: '16px' }}></Grid> <ServerStatus /> <Grid item xs={12} style={{ marginBottom: '16px' }}></Grid> <ServerGroupStatus /> <Grid item xs={12} style={{ marginBottom: '16px' }}></Grid> <DatabaseStatus /> <Grid item xs={12} style={{ marginBottom: '16px' }}></Grid> <WebServiceStatus /> </Grid> </SnackbarProvider> ); }
- 修改每个子组件(以ServerStatus为例),移除内部的Snackbar状态,改用
useSnackbar钩子调用:
import React, { useState, useEffect } from 'react'; import { Grid, Card, CardHeader, CardContent, Table, TableHead, TableRow, TableCell, TableBody, Button, Skeleton, useSnackbar } from '@mui/material'; import { Refresh as RefreshIcon, ZoomIn as ZoomInIcon } from '@mui/icons-material'; import { getServers } from "../api/server-service"; import { useTheme } from '@mui/material/styles'; function ServerStatus() { const theme = useTheme(); const [loading, setLoading] = useState(true); const [serverData, setServerData] = useState([]); const { enqueueSnackbar } = useSnackbar(); useEffect(() => { fetchServers(); }, []); const handleRefresh = () => { setLoading(true); fetchServers(); }; const fetchServers = () => { getServers() .then((response) => { console.log("Server data:", response.data); setTimeout(() => { setServerData(response.data); setLoading(false); if (response.data.length > 0) { enqueueSnackbar("Successfully fetched all Servers.", { severity: "success" }); } }, 1000); }) .catch((error) => { console.log(error); enqueueSnackbar("Error fetching servers: " + error.message, { severity: "error" }); }); }; const handleZoomInClick = () => { console.log('Zoom in clicked'); }; return ( <Grid item xs={12}> <Card elevation={3}> <CardHeader title="Server Status" action={ <Button variant="contained" color="secondary" startIcon={<RefreshIcon />} onClick={handleRefresh} > Refresh </Button> } /> <CardContent> <Table> <TableHead> <TableRow> <TableCell>Server Name</TableCell> <TableCell>Host</TableCell> <TableCell>Active Status</TableCell> <TableCell></TableCell> </TableRow> </TableHead> <TableBody> {loading ? ( <TableRow> <TableCell colSpan={4}> <Skeleton animation="wave" /> <Skeleton animation="wave" /> <Skeleton animation="wave" /> <Skeleton animation="wave" /> </TableCell> </TableRow> ) : serverData.length === 0 ? ( <TableRow> <TableCell colSpan={4}>Add Monitor Server Request To check status...</TableCell> </TableRow> ) : ( serverData.map((server) => ( <TableRow key={server.serverName}> <TableCell>{server.name}</TableCell> <TableCell>{server.host}</TableCell> <TableCell> <Button variant="contained" style={{ backgroundColor: server.isActive ? theme.palette.success.main : theme.palette.error.main, color: 'white', }} disabled={!server.isActive} > {server.isActive ? "Active" : "InActive"} </Button> </TableCell> <TableCell> <ZoomInIcon onClick={handleZoomInClick} style={{ cursor: 'pointer' }} /> </TableCell> </TableRow> )) )} </TableBody> </Table> </CardContent> </Card> </Grid> ); } export default ServerStatus;
其他子组件(ServerGroupStatus、DatabaseStatus等)做同样修改即可,Provider会自动处理所有Snackbar的堆叠和关闭逻辑。
方法2:手动设置每个Snackbar的偏移量
如果不想使用全局Provider,可以给每个组件的Snackbar设置不同的垂直偏移,让它们错开位置:
修改每个组件的Snackbar,添加offset属性或自定义样式:
- ServerStatus的Snackbar:
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'right' }} open={snackbarOpen} autoHideDuration={3000} onClose={handleCloseSnackbar} offset={{ top: 20 }} > <Alert onClose={handleCloseSnackbar} severity={snackbarSeverity}> {snackbarMessage} </Alert> </Snackbar>
- ServerGroupStatus的Snackbar:
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'right' }} open={snackbarOpen} autoHideDuration={3000} onClose={handleCloseSnackbar} offset={{ top: 80 }} > <Alert onClose={handleCloseSnackbar} severity={snackbarSeverity}> {snackbarMessage} </Alert> </Snackbar>
- DatabaseStatus的Snackbar:
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'right' }} open={snackbarOpen} autoHideDuration={3000} onClose={handleCloseSnackbar} offset={{ top: 140 }} > <Alert onClose={handleCloseSnackbar} severity={snackbarSeverity}> {snackbarMessage} </Alert> </Snackbar>
这种方法需要手动维护每个Snackbar的偏移值,新增组件时需要调整偏移量,灵活性不如第一种方法。
内容的提问来源于stack exchange,提问作者Nitish
相关产品推荐
相关产品推荐

