Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

It's it possible to get React to move an element rather than re-create it when it changes its place in the DOM?

It's it possible to get React to move an element rather than re-create it when it changes its place in the DOM?

Let's imagine I'm making a 2 pane component and I want to be able to hide/unhide one pane. Let's also imagine the panes themselves are very heavy. In my case the each pane has over 2000 elements.

In my actual code I'm using a splitter when there are 2 panes. In order to show just one pane I need to remove the splitter and replace it with a div.

The code below simulates this. If there's one pane it uses a div to contain the pane. If there's 2 panes it uses pre to contain them. In my case it would be div with 1 pain and a splitter with 2.

So, instrumenting document.createElement I see that not only are the containers created but the elements inside are recreated. In other words, in my code when go from splitter->div the 2000+ element pane will get entirely recreated which is slow.

Is there a way to tell React effectively. "Hey, don't recreate this component, just move it?"

class TwoPanes extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const panes = this.renderPanes();
    if (panes.length === 2) {
      return React.createElement('pre', {className: "panes"}, panes);
    } else {
      return React.createElement('div', {className: "panes"}, panes);
    }
  }
  renderPanes() {
    return this.props.panes.map(pane => {
      return React.createElement('div', {className: "pane"}, pane);
    });
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      panes: [
        "pane one",
        "pane two",
      ],
    };
  }
  render() {
    const panes = React.createElement(TwoPanes, {panes: this.state.panes}, null);
    const button = React.createElement('button', {
      onClick: () => {
        const panes = this.state.panes.slice();
        if (panes.length === 1) {
          panes.splice(0, 0, "pane one");  // insert pane 1
        } else {
          panes.splice(0, 1);  // remove pane 1
        }
        this.setState({panes: panes});
      },
    }, "toggle pane one");
    return React.createElement('div', {className: "outer"}, [panes, button]);
  }
}

// wrap document.createElement so we can see if new elements are created
// vs reused
document.createElement = (function(oldFn) {
  let count = 0;
  let inside = false;
  return function(type, ...args) {
    if (!inside) {  // needed because SO's console wrapper calls createElement
      inside = true;  
      console.log(++count, "created:", type);
      inside = false;
    }
    return oldFn.call(this, type, ...args);
  }
}(document.createElement));
  
  
ReactDOM.render(
  React.createElement(App, {}, null),
  document.getElementById('root')        
);
html { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; }
body { margin: 0; }
#root { width: 100vw; height: 100vh; }
.outer { 
  width: 100%; 
  height: 100%; 
}
.panes { 
  width: 100%; 
  height: 100%; 
  display: flex;
  flow-direction: row;
  justify-content: space-between;
}
.pane {
  flex: 1 1 auto;
  border: 1px solid black;
}
button {
  position: absolute;
  left: 10px;
  top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
like image 690
gman Avatar asked Dec 13 '16 15:12

gman


People also ask

How does React update the DOM?

React follows the observable pattern and listens for state changes. When the state of a component changes, React updates the virtual DOM tree. Once the virtual DOM has been updated, React then compares the current version of the virtual DOM with the previous version of the virtual DOM. This process is called “diffing”.

Does React use the DOM?

React uses virtual DOM to enhance its performance. It uses the observable to detect state and prop changes. React uses an efficient diff algorithm to compare the versions of virtual DOM. It then makes sure that batched updates are sent to the real DOM for repainting or re-rendering of the UI.

Does React re-render when props change?

React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.


1 Answers

I don't think there is a way to just move around DOM trees, even if there was - it will be pretty expensive, as

  • React's diff algorithm still needs to compare different trees and if it stumbles upon a subtree / node with different structure, it will immediately discard the old one. This is one of the assumptions that the diff algorithm makes in order to run in O(n)

Two elements of different types will produce different trees

  • In order to move around a cached DOM, you will first need detach it from the tree, which implies that it still needs to be reapplied later and that is a bottleneck. Inserting HTML into the DOM is very expensive, even if cached / prerendered.

My suggestion is to use CSS, because display: none / display: block is a much faster than reapplying cached DOM.

class TwoPanes extends React.Component {

  render() {
    return (
      <div> 
       <Pane1 />
       <Pane2 style={this.state.panes.length === 2 ? {} : {display: 'none'} } />
     </div>
    );
  }
}
like image 174
Lyubomir Avatar answered Oct 03 '22 13:10

Lyubomir