我正在尝试将 Material UI v5 SnackbarAlert 的逻辑提取到可重用的组件中。我发现最近回答了一个非常类似的问题,但是我的应用程序正在使用 JavaScript。
我已尝试将此调整为 JavaScript,但我在打开/关闭警报 Snackbar 时多次重新渲染组件时遇到问题。
到目前为止我的代码:
// src/AlertSnackbar.jsx
import React, { useEffect, useState } from 'react'
import Snackbar from '@mui/material/Snackbar';
import MuiAlert from '@mui/material/Alert';
const Alert = React.forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
export default function AlertSnackbar({message, ...otherProps}) {
const [content, setContent] = useState(undefined);
const [open, setOpen] = useState(false)
const [pack, setPack] = useState([])
const handleClose = () => {
setOpen(false);
}
//update content pack
useEffect(()=> {
message && setPack((prev) => [...prev, { message, key: new Date().getTime() }]);
}, [message])
//handle consecutive snackbars
useEffect(() => {
if (pack.length && !content) {
//set a new snack when no active snack
setContent({...pack[0]})
setPack((prev)=> prev.slice(1))
setOpen(true)
} else if (pack.length && content && open) {
//Close an active snack when a new one is added
setOpen(false)
}
}, [pack, content, open])
const handleExited = () => {
setContent(undefined);
};
return (
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose} {...otherProps}
TransitionProps={{ onExited: handleExited }} key={content?.key }
>
<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
<div>{content?.message}</div>
</Alert>
</Snackbar>
)
}
用途:
// src/SomeComponent.jsx
import React, { useState } from 'react'
import { Button } from '@mui/material'
import AlertSnackbar from '../components/AlertSnackbar'
export default SomeComponent = () => {
const [snackContent, setSnackContent] = useState(<></>)
const handleTestClick = () => setSnackContent(<>Hello, world!</>);
return (
<>
<Button onClick={handleTestClick}>Test</Button>
<AlertSnackbar message={snackContent} anchorOrigin={{ horizontal: "center", vertical: "bottom" }} />
</>
)
}
任何帮助将不胜感激!
const [snackContent, setSnackContent] = useState('');
const handleClose = () => {
setOpen(false);
setContent(undefined);
};
useEffect(() => {
if (pack.length && !content) {
setContent({ ...pack[0] });
setPack(prev => prev.slice(1));
setOpen(true);
} else if (pack.length && content && open && pack[0].key !== content.key) {
setOpen(false);
}
}, [pack, content, open]);
通过这些更改,您的代码应该可以正常工作。这是完整的 AlertSnackbar 组件:
// src/AlertSnackbar.jsx
import React, { useEffect, useState } from 'react';
import Snackbar from '@mui/material/Snackbar';
import MuiAlert from '@mui/material/Alert';
const Alert = React.forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
export default function AlertSnackbar({ message, ...otherProps }) {
const [content, setContent] = useState(undefined);
const [open, setOpen] = useState(false);
const [pack, setPack] = useState([]);
const handleClose = () => {
setOpen(false);
setContent(undefined);
};
useEffect(() => {
message && setPack(prev => [...prev, { message, key: new Date().getTime() }]);
}, [message]);
useEffect(() => {
if (pack.length && !content) {
setContent({ ...pack[0] });
setPack(prev => prev.slice(1));
setOpen(true);
} else if (pack.length && content && open && pack[0].key !== content.key) {
setOpen(false);
}
}, [pack, content, open]);
const handleExited = () => {
setContent(undefined);
};
return (
<Snackbar
open={open}
autoHideDuration={6000}
onClose={handleClose}
{...otherProps}
TransitionProps={{ onExited: handleExited }}
key={content?.key}
>
<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
<div>{content?.message}</div>
</Alert>
</Snackbar>
);
}
最后,如果我用 React.Fragment 包装警报消息,我就可以让连续警报小吃栏正常工作。仅使用字符串是行不通的。
像这样触发另一个页面上的小吃栏:
//src/SomeComponent.jsx
const [snackContent, setSnackContent] = useState("")
const handleBtnOne= () => setSnackContent("Hello, world!"); //doesn't work
const handleBtnTwo = () => setSnackContent(<>Goodbye World</>); //does work
return (
<>
<Button onClick={handleBtnOne}>One</Button>
<Button onClick={handleBtnTwo }>Two</Button>
<AlertSnackbar message={snackContent} />
</>
)
但我不确定这样使用 React.fragment 片段是否有效?
//src/AlertComponent.jsx
import React, { useEffect, useState } from 'react'
import Snackbar from '@mui/material/Snackbar';
import MuiAlert from '@mui/material/Alert';
const Alert = React.forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
export default function AlertSnackbar({message, ...otherProps}) {
const [content, setContent] = useState(undefined);
const [open, setOpen] = useState(false)
const [pack, setPack] = useState([])
const handleClose = () => {
setOpen(false);
setContent(undefined)
}
//update content pack
useEffect(()=> {
message && setPack((prev) => [...prev, { message, key: new Date().getTime()}]);},
[message])
//handle consecutive snackbars
useEffect(() => {
if (pack.length && !content) {
//set a new snack when no active snack
setContent({...pack[0]})
setPack((prev)=> prev.slice(1))
setOpen(true)
} else if (pack.length && content && open && pack[0].key!==content.key) {
//Close an active snack when a new one is added
setOpen(false)
}
}, [pack, content, open])
const handleExited = () => {
setContent(undefined);
};
return (
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}
TransitionProps={{ onExited: handleExited }} key={content?.key }
>
<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
<div>{content?.message}</div>
</Alert>
</Snackbar>
)
看起来 Material UI 有一个这样的工作示例(没有警报),但您可以向其中添加警报,它工作得很好。
参考链接:https://mui.com/material-ui/react-snackbar/#consecutive-snackbars
这是一个 Next.js 客户端组件示例:
'use client';
import { useEffect, useState } from 'react';
import { Alert, Button, Slide, SlideProps, Snackbar } from '@mui/material';
type TransitionProps = Omit<SlideProps, 'direction'>;
const SlideTransition = (props: TransitionProps) => <Slide {...props} direction="up" />;
export interface SnackbarMessage {
message: string;
key: number;
}
export interface State {
open: boolean;
snackPack: readonly SnackbarMessage[];
messageInfo?: SnackbarMessage;
}
const Alerts = (}) => {
const [snackPack, setSnackPack] = useState<readonly SnackbarMessage[]>([]);
const [open, setOpen] = useState(false);
const [messageInfo, setMessageInfo] = useState<SnackbarMessage | undefined>(undefined);
const handleClick = (message: string) => () => {
setSnackPack(prev => [...prev, { key: new Date().getTime(), message }]);
};
const onClose = () => {
setOpen(false);
};
const handleExited = () => {
setMessageInfo(undefined);
};
useEffect(() => {
if (snackPack.length && !messageInfo) {
// Set a new snack when we don't have an active one
setMessageInfo({ ...snackPack[0] });
setSnackPack(prev => prev.slice(1));
setOpen(true);
} else if (snackPack.length && messageInfo && open) {
// Close an active snack when a new one is added
setOpen(false);
}
}, [snackPack, messageInfo, open]);
return (
<>
<Button onClick={handleClick('Message A')}>Show message A</Button>
<Button onClick={handleClick('Message B')}>Show message B</Button>
<Snackbar
TransitionComponent={SlideTransition}
TransitionProps={{ onExited: handleExited }}
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
key={messageInfo ? messageInfo.key : undefined}
message={messageInfo ? messageInfo.message : undefined}
onClose={onClose}
open={open}
>
{messageInfo ? (
<Alert elevation={2} icon={false} onClose={onClose} severity="success">
{messageInfo ? messageInfo.message : undefined}
</Alert>
) : undefined}
</Snackbar>
</>
);
};
export default Alerts;