ademilter/bricklayer

React.js errors when updating a component which has Bricklayer content in it

Opened this issue · 1 comments

I am unable to make React work with Bricklayer reliably. Problem happens when you mount a component which has Bricklayer content in it and update the component w/o unmounting.

Repro Steps

git clone git@github.com:tugberkugurlu/ReactJsSamples.git
cd ReactJsSamples/BricklayerRepro
git checkout -qf f8cf2f2d958e597cb9aa3129c7d5c2f045f7f3f9
yarn
npm start

Open the app on http://localhost:3000 and fire up Chrome Dev Tools as well to see the errors. The app initially loads random items and bricklayer nicely shapes them (see componentDidMount). However, HomePage component updates the content of ItemsList component without unmounting it in every 3 seconds. That's why I have the destroy and reinitialize code inside componentDidUpdate.

However, any update that ItemsList receives makes react upset and it fails even before getting to componentDidUpdate and it's not able to remove any elements from the DOM and it builds up indefinitely like that.

The first error is the below one:

DOMChildrenOperations.js:67 Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.removeChild @ DOMChildrenOperations.js:67processUpdates @ DOMChildrenOperations.js:183dangerouslyProcessChildrenUpdates @ ReactDOMIDOperations.js:30processQueue @ ReactMultiChild.js:139_updateChildren @ ReactMultiChild.js:359updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129_updateRenderedComponent @ ReactCompositeComponent.js:784_performComponentUpdate @ ReactCompositeComponent.js:753updateComponent @ ReactCompositeComponent.js:670receiveComponent @ ReactCompositeComponent.js:564receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129_updateRenderedComponent @ ReactCompositeComponent.js:784_performComponentUpdate @ ReactCompositeComponent.js:753updateComponent @ ReactCompositeComponent.js:670performUpdateIfNecessary @ ReactCompositeComponent.js:578performUpdateIfNecessary @ ReactReconciler.js:163runBatchedUpdates @ ReactUpdates.js:151perform @ Transaction.js:138perform @ Transaction.js:138perform @ ReactUpdates.js:90flushBatchedUpdates @ ReactUpdates.js:173closeAll @ Transaction.js:204perform @ Transaction.js:151batchedUpdates @ ReactDefaultBatchingStrategy.js:63enqueueUpdate @ ReactUpdates.js:201enqueueUpdate @ ReactUpdateQueue.js:25enqueueSetState @ ReactUpdateQueue.js:210ReactComponent.setState @ ReactComponent.js:64(anonymous function) @ index.js:103
16DOMChildrenOperations.js:67 Uncaught TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'.removeChild @ DOMChildrenOperations.js:67processUpdates @ DOMChildrenOperations.js:183dangerouslyProcessChildrenUpdates @ ReactDOMIDOperations.js:30processQueue @ ReactMultiChild.js:139_updateChildren @ ReactMultiChild.js:359updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129_updateRenderedComponent @ ReactCompositeComponent.js:784_performComponentUpdate @ ReactCompositeComponent.js:753updateComponent @ ReactCompositeComponent.js:670receiveComponent @ ReactCompositeComponent.js:564receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129_updateRenderedComponent @ ReactCompositeComponent.js:784_performComponentUpdate @ ReactCompositeComponent.js:753updateComponent @ ReactCompositeComponent.js:670performUpdateIfNecessary @ ReactCompositeComponent.js:578performUpdateIfNecessary @ ReactReconciler.js:163runBatchedUpdates @ ReactUpdates.js:151perform @ Transaction.js:138perform @ Transaction.js:138perform @ ReactUpdates.js:90flushBatchedUpdates @ ReactUpdates.js:173closeAll @ Transaction.js:204perform @ Transaction.js:151batchedUpdates @ ReactDefaultBatchingStrategy.js:63enqueueUpdate @ ReactUpdates.js:201enqueueUpdate @ ReactUpdateQueue.js:25enqueueSetState @ ReactUpdateQueue.js:210ReactComponent.setState @ ReactComponent.js:64(anonymous function) @ index.js:103

After that, subsequent errors are the same as below one for each update that happens every 3 seconds:

DOMChildrenOperations.js:67 Uncaught TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'.removeChild @ DOMChildrenOperations.js:67processUpdates @ DOMChildrenOperations.js:183dangerouslyProcessChildrenUpdates @ ReactDOMIDOperations.js:30processQueue @ ReactMultiChild.js:139_updateChildren @ ReactMultiChild.js:359updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129_updateRenderedComponent @ ReactCompositeComponent.js:784_performComponentUpdate @ ReactCompositeComponent.js:753updateComponent @ ReactCompositeComponent.js:670receiveComponent @ ReactCompositeComponent.js:564receiveComponent @ ReactReconciler.js:129updateChildren @ ReactChildReconciler.js:107_reconcilerUpdateChildren @ ReactMultiChild.js:213_updateChildren @ ReactMultiChild.js:316updateChildren @ ReactMultiChild.js:303_updateDOMChildren @ ReactDOMComponent.js:960updateComponent @ ReactDOMComponent.js:780receiveComponent @ ReactDOMComponent.js:734receiveComponent @ ReactReconciler.js:129_updateRenderedComponent @ ReactCompositeComponent.js:784_performComponentUpdate @ ReactCompositeComponent.js:753updateComponent @ ReactCompositeComponent.js:670performUpdateIfNecessary @ ReactCompositeComponent.js:578performUpdateIfNecessary @ ReactReconciler.js:163runBatchedUpdates @ ReactUpdates.js:151perform @ Transaction.js:138perform @ Transaction.js:138perform @ ReactUpdates.js:90flushBatchedUpdates @ ReactUpdates.js:173closeAll @ Transaction.js:204perform @ Transaction.js:151batchedUpdates @ ReactDefaultBatchingStrategy.js:63enqueueUpdate @ ReactUpdates.js:201enqueueUpdate @ ReactUpdateQueue.js:25enqueueSetState @ ReactUpdateQueue.js:210ReactComponent.setState @ ReactComponent.js:64(anonymous function) @ index.js:103

This is what I see in this repro example. However, in my actual application, I am observing the below error on the same issue which is a bit different:

VM26259:1 Uncaught TypeError: error.stack is not a function
    at eval (eval at evaluate (unknown source), <anonymous>:1:7)
    at eval (webpack:///./src/components/common/formUtils.js?:17:13)(anonymous function) @ VM26259:1(anonymous function) @ formUtils.js:17
error
TypeError: Cannot read property 'getHostNode' of null
    at Object.getHostNode (webpack:///./~/react/lib/ReactReconciler.js?:64:28)
    at ReactCompositeComponentWrapper.getHostNode (webpack:///./~/react/lib/ReactCompositeComponent.js?:383:28)
    at Object.getHostNode (webpack:///./~/react/lib/ReactReconciler.js?:64:29)
    at Object.updateChildren (webpack:///./~/react/lib/ReactChildReconciler.js?:130:46)
    at ReactDOMComponent._reconcilerUpdateChildren (webpack:///./~/react/lib/ReactMultiChild.js?:210:32)
    at ReactDOMComponent._updateChildren (webpack:///./~/react/lib/ReactMultiChild.js?:314:31)
    at ReactDOMComponent.updateChildren (webpack:///./~/react/lib/ReactMultiChild.js?:301:12)
    at ReactDOMComponent._updateDOMChildren (webpack:///./~/react/lib/ReactDOMComponent.js?:942:12)
    at ReactDOMComponent.updateComponent (webpack:///./~/react/lib/ReactDOMComponent.js?:760:10)
    at ReactDOMComponent.receiveComponent (webpack:///./~/react/lib/ReactDOMComponent.js?:718:10)

Sample Code

Adding the code used for repro steps here just for reference. However, it has dependencies as you can see. So, it will not work as is. Look at here to make it work with the above repro steps.

import './bootswatch.less';
import 'bricklayer/dist/bricklayer.css';
import './index.scss';
import React from 'react';
import ReactDom from 'react-dom';
import Bricklayer from 'bricklayer';
import loremIpsum from 'lorem-ipsum';
import _ from 'underscore';

const ItemsList = React.createClass({
    componentDidMount: function() {
        this.itemsListContainerBricklayer = new Bricklayer(this.refs.itemsListContainer);
    },
    componentDidUpdate: function() {
        this.itemsListContainerBricklayer.destroy();
        this.itemsListContainerBricklayer = new Bricklayer(this.refs.itemsListContainer);
    },
    render: function() {
        const { items } = this.props;
        return <div className="search-results-container">
            <div className="row">
                <div className="col-xs-12">
                    <div className="bricklayer cards-list" ref="itemsListContainer">
                        {items.map(item =>
                            <div key={item.id} className="card">{item.content}</div> 
                        )}
                    </div>
                </div>
            </div>
        </div>;
    }
});

export const guid = () => {
    const s4 = () => {
        return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1);
    };

    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
        s4() + '-' + s4() + s4() + s4();
};

const getRandomItems = () => {
    const count = _.random(20, 100);
    return _(_.range(count)).map(i => ({
        id: guid(),
        content: loremIpsum()
    }));
};

const HelloWorld = React.createClass({
    getInitialState: function() {
        return {
            items: getRandomItems()
        };
    },
    componentDidMount: function() {
        this.timerId = window.setInterval(() => {
            this.setState({
                items: getRandomItems()
            });
        }, 3000);
    },
    componentWillUnmount: function() {
        window.clearInterval(this.timerId);
    },
    render: function() {
        return <div>
            <ItemsList items={this.state.items} />
        </div>;
    }
});

ReactDom.render(
    <HelloWorld />, 
    document.getElementById('app')
);

Forcing the DOM part, which Bricklayer has operated on, to be unmounted (with a hacky way 😞) workaround the issue but not a great solution: https://github.com/tugberkugurlu/ReactJsSamples/blob/9cb410ed92c8066b5d046dd976909443d5d63542/BricklayerRepro/src/index.js#L10-L58

My feeling is that Bricklayer somehow removes some things that React put in place (e.g. key of the DOM element like here, etc.) and that makes React send when it tries to remove the nodes during the Reconsilation process.