Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Non blocking render in ReactJS

I'm learning ReactJS and trying to build some application on it.

When I'm trying to modify my state and render, my page is freezing and can't do anything until the render is finished when my components become huge.

I found that I can use shouldComponentUpdate to optimize my code, but the question comes to me is: Can I make this render procedure be non blocking? And so I can tell the user that the page is processing some heavy loading executions and please wait or maybe show the progress of the execution? Or if the user can cancel the render, for example, for a live editor, if user edit the content of the editor, the "preview" section will stop rendering old content and trying to render new content without blocking the editor UI?

Here's the heavy loading example code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Tutorial</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel">
      var Box = React.createClass({
        render: function() {
          return (
            <div>Box</div>
          );
        }
      });
      
      var CommentBox = React.createClass({
        getInitialState: function() {
          return {box_count: 5};
        },
        
        heavyLoadRender: function() {
          this.setState({box_count: 40000});
        },
        
        render: function() {
          var render_box = [];
          for (var i=0; i<this.state.box_count; i++) {
            render_box.push(<Box />);
          }
          return (
            <div>
              {render_box}
              <button onClick={this.heavyLoadRender}>Start</button>
            </div>
          );
        }
      });
      
      ReactDOM.render(
        <CommentBox />,
        document.getElementById('content')
      );
    </script>
  </body>
</html>

When I press Start, the page will freeze and no response until all Box is rendered. Is it possible to add a button named Cancel which user can cancel the render and clear all boxes?

like image 407
Nier Avatar asked Apr 19 '16 04:04

Nier


People also ask

What is conditional rendering in React?

In React, you can create distinct components that encapsulate behavior you need. Then, you can render only some of them, depending on the state of your application. Conditional rendering in React works the same way conditions work in JavaScript.

What is suspense and lazy in React?

Suspense is a component for wrapping lazy components. You can wrap multiple lazy components at different hierarchy levels with a single Suspense component. The Suspense component takes a fallback prop that accepts the React elements you want rendered as placeholder content while all the lazy components get loaded.

Is JS render blocking?

Render-blocking resources are portions of code in website files, usually CSS and JavaScript, that prevent a web page from loading quickly. These resources take a relatively long time for the browser to process, but may not be necessary for the immediate user experience.


1 Answers

This is a great question, and a perfect use case for setTimeout which can schedule an update to the next round of the event loop.

Rather than store the number of components to render, store an array of components, and render them directly. jsfiddle

 var CommentBox = React.createClass({
    getInitialState: function() {
      return { boxes: [<Box key="first" />] };
    },

    heavyLoadRender: function() {
      setTimeout(() => {
        if (this.state.boxes.length < 50000) {
          this.setState({ 
            boxes: this.state.boxes.concat(<Box key={this.state.boxes.length} />)
          })
          this.heavyLoadRender()
        }
      })
    },

    render: function() {
      return (
        <div>
          <button onClick={this.heavyLoadRender}>Start</button>
          {this.state.boxes}
        </div>
      )
    }
  })

Update:

If you only want to show the state once the array is filled up, don't display anything until it hits that size:

This did not work:

{ this.state.boxes.length === 50000 && this.state.boxes }

Hope is not lost though! Use style!

<div style={{ display: this.state.boxes.length === 50000 ? 'block' : 'none' }}>
  { this.state.boxes }
</div>

If you want to increase the speed, you can push more than item per setTimeout

var newBoxes = []
for (var i = 0; i < 5; i++) {
   newBoxes.push(<Box />)
}
this.setState({ 
  boxes: this.state.boxes.concat(newBoxes)
})

updated fiddle. I think this whole class of problems is going to take time to perform. In batches of 10,000 the basic box component doesn't block and you could easily throw a loading spinner up there.

like image 57
azium Avatar answered Oct 22 '22 20:10

azium