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

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的显示,自动处理堆叠逻辑,无需手动调整位置。

  1. 修改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>
  );
}
  1. 修改每个子组件(以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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 14:45:54