如何在Javascript中使用React和Material UI v5创建连续的snackbar/alert组件?

问题描述 投票:0回答:3

我正在尝试将 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" }} />
    </>
  )
}

任何帮助将不胜感激!

reactjs material-ui alert snackbar
3个回答
0
投票
  1. 我发现您正在使用复杂的 JSX 元素作为 SomeComponent 中的 SnackContent 状态的初始值。相反,您应该将其初始化为空字符串或任何其他合适的初始值,如下所示:
const [snackContent, setSnackContent] = useState('');
  1. 您在调用时将 open 设置为 false,但这不会清除内容状态。相反,您还应该在 Snackbar 关闭时将内容设置为未定义,如下所示:
const handleClose = () => {
      setOpen(false);
      setContent(undefined);
    };
  1. 条件 if (pack.length && content && open) 不涵盖 pack 数组中有多个项目的情况。相反,您应该检查包数组是否已更改,如下所示:
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>
  );
}

0
投票

最后,如果我用 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>
  )

0
投票

看起来 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;

© www.soinside.com 2019 - 2024. All rights reserved.