首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ReactJS:为什么我的自定义功能吐司组件在试图自动拒绝通知弹出时表现得如此奇怪?

ReactJS:为什么我的自定义功能吐司组件在试图自动拒绝通知弹出时表现得如此奇怪?
EN

Stack Overflow用户
提问于 2022-07-01 22:54:18
回答 1查看 564关注 0票数 0

我的自定义反应性吐司组件运行良好,直到我尝试在设定的时间后实现自动取消通知。

我试图做到这样,在一个固定的时间后,弹出的“祝酒词”通知将有一个CSS淡出动画播放,然后被删除-除非用户悬停在该通知,在这种情况下,它将推迟删除,直到用户移动他们的鼠标离开它。

有时它正常工作,另一些时候它停止显示任何东西并一个一个地添加通知,其他时候它.它只是以一种非常奇怪和意想不到的方式表现出来。

这是我的代码:

Toast.css

代码语言:javascript
复制
.toast-container {
    font-size: 24px;
    box-sizing: border-box;
    position: fixed;
    z-index: 10;
}

.toast-popup {
    padding: 12px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 500px;
    border: solid #f2f2f2;
    border-radius: 8px;
    box-shadow: 0 0 10px #999;
    margin-bottom: 1rem;
    opacity: 0.9;
}
.toast-popup:hover {
    box-shadow: 0 0 12px deepskyblue;
    opacity: 1 !important;
    animation-play-state: paused;
}

.success {
    background-color: #5cb85c;
}
.info {
    background-color: #5bc0de;
}
.warning {
    background-color: #f0ad4e;
}
.danger {
    background-color: #d9534f;
}

.toast-text {
    justify-self: flex-start;
    width: 100%;
    padding: 6px 0 6px 6px;
    opacity: 0.9;
}
.toast-title {
    font-weight: 700;
    font-size: 32px;
    text-align: left;
    padding-bottom: 0px;
    color: #f2f2f2;
}

.toast-message {
    padding-top: 0px;
    text-align: left;
    color: #f2f2f2;
}

.toast-icon {
    float: left;
    margin: 0 20px 0 10px;
    opacity: 0.9;
}
.toast-icon img {
    width: 50px;
    height: 50px;
    fill: #f2f2f2;
    opacity: 0.9;
}

.close-button {
    float: right;
    align-self: flex-start;
    font-weight: 600;
    color: #f2f2f2;
    background: none;
    border: none;
    opacity: 0.9;
    cursor: pointer;
}

.top-right {
    top: 2rem;
    right: 2rem;
}
.top-right-slide {
    top: 2rem;
    right: 2rem;
    transition: transform .6s ease-in-out;
    animation: toast-in-right .7s;
}

.bottom-right {
    bottom: 2rem;
    right: 2rem;
}
.bottom-right-slide {
    bottom: 2rem;
    right: 2rem;
    transition: transform .6s ease-in-out;
    animation: toast-in-right .7s;
}

.top-left {
    top: 2rem;
    left: 2rem;
}
.top-left-slide {
    top: 2rem;
    left: 2rem;
    transition: transform .6s ease-in;
    animation: toast-in-left .7s;
}

.bottom-left {
    bottom: 2rem;
    left: 2rem;
}
.bottom-left-slide {
    bottom: 2rem;
    left: 2rem;
    transition: transform .6s ease-in;
    animation: toast-in-left .7s;
}

.fadeout {
    animation: 4s linear 5s 1 normal forwards running toast-fadeout;
}

@keyframes toast-in-right {
    from { transform: translateX(100%); }
    to { transform: translateX(0); }
}

@keyframes toast-in-left {
    from { transform: translateX(-100%); }
    to { transform: translateX(0); }
}

@keyframes toast-fadeout {
    from { opacity: 0.9; }
    to { opacity: 0; }
}

Toast.js -请原谅console.logs的慷慨激昂.

代码语言:javascript
复制
import React, {useEffect, useState} from 'react';
import icon_success from './icons/feathericons/check-circle.svg';
import icon_info from './icons/feathericons/info.svg';
import icon_warning from './icons/feathericons/alert-triangle.svg';
import icon_danger from './icons/feathericons/alert-octagon.svg';
import './Toast.css';

const Toast = (props) => {
    const {toastList, position} = props;
    const [list, setList] = useState(toastList);
    const [prevId, setPrevId] = useState(0);

    // This useEffect updates the list of toasts to display
    useEffect(() => {
        console.log('useEffect()');
        console.log('useEffect() toastList:');
        console.log(toastList);
        setList([...toastList]);
    }, [toastList]);

    const markForDeletion = (toast) => {
        if( toast.isDeleting ) {
            return;
        }
        console.log(`toast ${toast.id} marked for deletion`)
        toast.isDeleting = true;
        setTimeout(() => {attemptDeletion(toast)}, 5000);
    }

    const attemptDeletion = (toast) => {
        console.log(`attempting to delete toast ${toast.id}. canDelete = ${toast.canDelete}`);
        if( toast.canDelete ) {
            deleteToast(toast.id);
        }
        else {
            console.log(`cannot delete toast ${toast.id}. `);
        }
    }

    const getIcon = (variant) => {
        switch( variant ) {
            case 'success':
                return icon_success;
                break;
            case 'info':
                return icon_info;
                break;
            case 'warning':
                return icon_warning;
                break;
            case 'danger':
                return icon_danger;
                break;
        }
    }

    const generateId = (toast) => {
        if( typeof(toast.id) === 'number' ) {
            return toast.id;
        }
        toast.id = prevId + 1;
        setPrevId(toast.id);
        return toast.id;
    }

    const deleteToast = (id) => {
        console.log(`deleting toast ${id}`);
        const deletionIdxList = list.findIndex(e => e.id === id);
        const deletionIdxToastList = toastList.findIndex(e => e.id === id);
        console.log(`deletionIdxToastList: ${deletionIdxToastList}`);
        if(deletionIdxList == null || deletionIdxList === -1) {
            console.log(`cannot find list idx of id ${id}`);
            console.log('list:');
            console.log(list);
            return;
        }
        if(deletionIdxToastList == null || deletionIdxToastList === -1) {
            console.log(`cannot find toastList idx of id ${id}`);
            console.log('toastList:');
            console.log(toastList);
            return;
        }
        
        console.log('list before deletion:');
        console.log(list);
        console.log('toastList before deletion:');
        console.log(toastList);
        console.log('list[deletionIdxList]:');
        console.log(list[deletionIdxList]);
        list.splice(deletionIdxList, 1);
        console.log('toastList[deletionIdxToastList]:');
        console.log(toastList[deletionIdxToastList]);
        toastList.splice(deletionIdxToastList, 1);
        setList([...list]);
        console.log(`toast ${id} deleted successfully`);
        console.log('list after deletion:');
        console.log(list);
        console.log('toastList after deletion:');
        console.log(toastList);
    }

    return (
        <>
            <div className={`toast-container ${position}`} >
                {
                    list.map((toast, i) => (
                        <div
                            key={i}
                            className={`toast-popup ${toast.variant} ${toast.isDeleting ? (position + ' fadeout') : (position + '-slide')}`}
                            onLoad={() => {
                                if( !toast.isLoaded ) {
                                    toast.Id = generateId(toast);
                                    toast.canDelete = true;
                                    toast.isDeleting = false;
                                    toast.isLoaded = true;
                                    console.log(`on load ${toast.id}`);
                                    setTimeout(() => markForDeletion(toast), 500);
                                }
                            }}
                            onMouseOver={() => {
                                toast.canDelete === true ? toast.canDelete = false : null;
                                toast.isDeleting === true ? toast.isDeleting = false : null;
                                console.log(`mouse over ${toast.id}`);
                            }}
                            onMouseLeave={() => {
                                toast.canDelete === false ? toast.canDelete = true : null;
                                markForDeletion(toast);
                                console.log(`mouse leave ${toast.id}`);
                            }}
                        >
                            <div className={'toast-icon'}>
                                <img src={getIcon(toast.variant)} />
                            </div>
                            <div className={'toast-text'}>
                                <div className={'toast-title'}>
                                    {toast.variant.charAt(0).toUpperCase() + toast.variant.slice(1)}
                                </div>
                                <div className={'toast-message'}>{toast.message}</div>
                            </div>
                            <button
                                className={'close-button'}
                                onClick={() => {
                                    toast.canDelete = true;
                                    deleteToast(toast.id)
                                }
                            }>
                                X
                            </button>
                        </div>
                    ))
                }
            </div>
        </>
    )
}

Toast.defaultProps = {
    position: 'bottom-right'
}

export default Toast;

片段的Home.js,在这里,我正在测试这个新的Toast组件-一个类组件,因为我正在更新一个预先存在的应用程序,以消除对react库的依赖。

代码语言:javascript
复制
// Leaving out constructor and other irrelevant code...
  toastSuccess() {
    const newToast = {
      variant: 'success',
      message: 'This is a test of the success variant toast pop-up.'
    }
    this.setState({
      toastList: [...this.state.toastList, newToast]
    });
  }
  toastInfo() {
    const newToast = {
      variant: 'info',
      message: 'This is a test of the info variant toast pop-up.'
    }
    this.setState({
      toastList: [...this.state.toastList, newToast]
    });
  }
  toastWarning() {
    const newToast = {
      variant: 'warning',
      message: 'This is a test of the warning variant toast pop-up.'
    }
    this.setState({
      toastList: [...this.state.toastList, newToast]
    });
  }
  toastDanger() {
    const newToast = {
      variant: 'danger',
      message: 'This is a test of the danger variant toast pop-up.'
    }
    this.setState({
      toastList: [...this.state.toastList, newToast]
    });
  }

render() {
    return (
      <div className="Home" style={{height:'100%'}}>
        <Toast
            toastList={this.state.toastList}
            position={'bottom-right'}
        />
        <div style={{display:'flex', justifyContent:'center'}}>
          <Button onClick={() => this.toastSuccess()}>Success</Button>
          <Button onClick={() => this.toastInfo()}>Info</Button>
          <Button onClick={() => this.toastWarning()}>Warning</Button>
          <Button onClick={() => this.toastDanger()}>Danger</Button>
        </div>
        {// ...}
      </div>
    );
  }

让我知道是否有方法让这段代码在StackOverflow上运行,使用这个代码片段特性,因为这会非常有用,这样读者就可以直接看到问题了。不幸的是,我从来没有任何运气让它工作,但我会继续努力,看看我是否能解决它。

编辑:

感谢@推荐StackBlitz作为一个良好的可共享测试环境。我已经把它设置在那里了,这里有一个链接:https://react-ts-ybunlg.stackblitz.io

EN

回答 1

Stack Overflow用户

发布于 2022-07-02 00:16:01

我在您的代码中看到的第一个问题是,您在吐司列表中保留了两个真相来源。一个是通过props从父节点传递的,另一个是Toast组件中的内部状态列表。这是一个反模式,可以产生许多问题。

第二个问题是,您正在更改从父级收到的列表。这在React中是一个巨大的反模式,因为props只读 -- 所有的反应组件都必须对它们的道具起纯函数的作用。(由于您正在更改数组中的一个对象,显然它适用于加载更新,但当您试图调用列表上的splice时,它不起作用) --这就是为什么即使您删除了元素并应用了删除效果,当它在父级(下一个呈现) ->上得到更新时,它也会在不被移除的情况下返回,再次单击另一个生成按钮将显示先前删除的吐司。

我认为这里的大问题是你没有正确地使用作文。与其将toast列表传递给Toast组件,不如将列表保留在父组件上,将map从父组件中移出。对于列表中的每个元素,您将有一个Toast组件的实例。

也许你也可以有一个ToastList组件,根据它们的位置来处理Toast组件.因此,例如,当您单击Upper Left Toast Generator时,它将在一个带有position键的祝酒词数组中添加一个新条目。该数组将被发送到ToastList组件,该组件将生成在内部处理其状态(删除等)并且不更新实际列表的Toast组件。您可以向Toast组件传递一个名为onDelete的函数,该函数将由Toast组件在删除时调用,并且您将根据这些事件更新ToastList状态(可能会将删除事件传播到父级以更新那里的列表)。

希望这有意义。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72835253

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档