React JS事件不会为最后渲染的元素触发

时间:2014-07-02 17:04:34

标签: javascript drag-and-drop reactjs

我创建了一个拖放界面,最终将其转换为表单生成器。我创建了表单构建器http://jsfiddle.net/szASZ/1/的小提琴。当您将其中一个顶部项目拖动到其下方的灰色拖放区域时,一切似乎都能正常工作。

但是,我遇到的问题是当您刷新页面/小提琴并尝试对放置区域内的项目进行排序时。如果你抓住"无线电输入"在第一个放置区域的顶部并将其移动到该放置区域内部或外部,一切正常。

现在,尝试拖动最后一项" Checkbox Input"在它的放置区域内,一切都应该正常工作。最后,刷新页面/小提琴并移动相同的,最后一个" Checkbox输入"到另一个下降区。占位符将显示,然后永远不会消失。我发现的是" onDragEnd"在拖放循环的这个实例期间永远不会调用event,但每隔一次调用它。此事件通常会传递到表单构建器的顶层,在该表单构建器中,它会拖动正在拖动的项目并将其设置在新的数据数组中。

为了让事情变得更有趣,如果我将一个空对象添加到包含已删除项目(http://jsfiddle.net/szASZ/ - 第30,34行)的数组上,一切都按预期工作,这样看起来就是最后一项在" drop zone"的数组中项目没有正确触发" onDragEnd"事件

componentWillMount: function () {
    var newInput = [
        { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Text Input" },
        { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Checkbox Input" },
        { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Radio Input" }
    ];

    var newZones = [
        [
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Radio Input" },
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Text Input" },
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Checkbox Input" }
            ,{}
        ],
        [
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Text Input" },
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Checkbox Input" }
            ,{}
        ]
    ];

    this.setState({ inputs: newInput });
    this.setState({ zones: newZones });
}

基本上,这是我遇到的主要问题,我不知道为什么每个其他项目都被排序,丢弃并且事件触发正确但最后一个?谢谢!

3 个答案:

答案 0 :(得分:5)

这与我几个月前提交的React错误直接相关/引起:

#1355: touchmove doesn't fire on removed element

React利用事件冒泡并绑定文档根目录中的所有事件,这在大多数情况下都有效,但在这种特殊情况下会出现问题。与鼠标事件不同,触摸和拖动事件始终发送到接收touchstartdragstart事件的事件(而不是指针当前所在的元素)。如果从DOM中删除了收到dragstart事件的元素(因为它在此特定情况下从它开始的列表中消失),那么该(分离的)元素仍将接收dragend事件,但它不会冒泡到文档根目录,所以React永远不会看到事件。

您只能在拖动最后一个元素时看到这一点,因为您未正确使用key道具。因为您当前使用索引作为键,当您有一个列表ABCD并开始拖动B(导致列表成为ACD)时,React会改变第二个元素(以前为B)以使文本" C& #34;,将第三个元素(以前的C)变为具有文本" D",并删除第四个元素(以前为D)。如果您使用唯一的每件商品ID作为键而不是索引,React将始终删除您要拖动的元素,这将更容易推理。 (由于该错误,您会发现此行为始终发生,而不是仅在拖动最后一个元素时发生。)

作为一种变通方法,您可以在dragstart处理程序中手动绑定dragend / dragenter / dragleave处理程序,并在dragend上清除它们。这将完全回避React的事件系统:

// DRAGGING
dragStart: function (e) {
    this.getDOMNode().addEventListener('dragend', this.dragEnd, false);
    this.getDOMNode().addEventListener('dragenter', this.dragEnter, false);
    this.getDOMNode().addEventListener('dragleave', this.dragLeave, false);

    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/html', e.target.innerHTML);

    this.props.dragStart(this.props.item, { sorting: true });
},
dragEnd: function (e) {
    this.getDOMNode().removeEventListener('dragend', this.dragEnd, false);
    this.getDOMNode().removeEventListener('dragenter', this.dragEnter, false);
    this.getDOMNode().removeEventListener('dragleave', this.dragLeave, false);

    var opts = e.dataTransfer.dropEffect === 'none' ? { success: false } : { success: true };

    this.props.dragEnd(opts);
},

使用该设置,您只需在onDragStart中指定render

(我知道我是否可以解决这个问题 - 据我所知,除了我以外,你是第一个打击它的人,所以它不是我们的优先考虑事项。 )

答案 1 :(得分:2)

我有一个类似的错误,onDragEnd没有为重新渲染的节点开​​火。

@Ben Alpert,谢谢你的解决方法,这太棒了!我建议稍加修改一下:



dragEnd: function(e) {

  // component can be umounted and getDOMNode will throw in that case
  if (this.isMounted()) {
    this.getDOMNode().removeEventListener('dragend', this.dragEnd, false);
    // ...
  }
  // ... invoke the callback
},




答案 2 :(得分:0)

我遇到了类似的问题并将其转换为功能组件。

function DragCell () {
    const [ DragTarget, SetDragTarget ] = React.useState<HTMLSpanElement>();

    // Manually bind the event, because if the component un-renders, we still want to fire the event
    React.useEffect(() => {
        if (!DragTarget) {
            return;
        }

        const onDragEnd = () => SetDragTarget(undefined);
        DragTarget.addEventListener('dragend', onDragEnd);
        return () => {
            // In the event the component is unloaded, we still want to call the clean up function
            // Put your code you want to run on dragEnd here
            DragTarget.removeEventListener('dragend', onDragEnd);
        };
    }, [DragTarget]);

    return (
        <td data-isdragging={ !!DragTarget } className='DragCell'>
            <span
                className='DragHandle'
                draggable
                onDragStart={event => {
                    event.dataTransfer.setData('dragging', '');
                    SetDragTarget(event.currentTarget);
                }}
            >Drag Handle</span>
        </td>
    );
};