question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

lazy loading is useless right now

See original GitHub issue

Lazy loading should make mounting the swipeable views component faster by only mounting not initially visible views later, but how lazy loading is implemented right now is that, on initial render only view of the chosen index is rendered – this is correct --, but on componentDidMount event, it sets the firstRender state to false, which triggers a synchronous re-render of this component. In this re-render, all the other views are mounted too. This makes lazy-loading completely useless, because in the end, all the views are rendered synchronously.

reference: https://reactjs.org/docs/react-component.html#componentdidmount

You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues. In most cases, you should be able to assign the initial state in the constructor() instead. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.

  • I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior

views not initially visible should be mounted asynchronously with either raf or setTimeout.

Current Behavior

views not initially visible are still mounted synchronously after componentDidMount event.

Steps to Reproduce (for bugs)

This can be observed in demo.

  1. go to demo page, https://n075p8mq7l.codesandbox.io/
  2. open chrome dev tools
  3. click “start profiling and reload page”
  4. check the user timing section and see that “Lifecycle hook scheduled a cascading update”, and all the views are mounted in the cascading update synchronously.

Context

When you put something huge into the views, performance is not acceptable without lazy loading.

Your Environment

Tech Version
react-swipeable-views 0.12.16
React 16.4.1
platform macOS
etc

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:5
  • Comments:22 (3 by maintainers)

github_iconTop GitHub Comments

456reactions
gaearoncommented, Sep 2, 2018

Normally React wants to keep the UI consistent with what you told it to render. So if you render

function Slideshow() {
  return (
    <div>
      <Expensive1 />
      <Expensive2 />
      <Expensive3 />
    </div>
  );
}

then it will have to call render methods and create DOM nodes for the whole trees of Expensive1, Expensive2, and Expensive3, when Slideshow is being mounted.

However, maybe you’re only showing Expensive1 on the first render, and Expensive2 and Expensive3 are not immediately visible. Like if this is really a side show.

Today, it’s idiomatic to render just the current node, e.g.

function Slideshow(props) {
  return (
    <div>
      {props.current === 0 && <Expensive1 />}
      {props.current === 1 && <Expensive2 />}
      {props.current === 2 && <Expensive3 />}
    </div>
  );
}

The upside of this is that now your first render of <Slideshow current={0} /> only includes Expensive1. However, the downside is that the moment you switch to <Slideshow current={1} />, you will experience jank from creating and rendering the whole Expensive2 tree.

Ideally what we want to do is to tell React that when we render <Slideshow current={0} />, we want to show Expensive1, but we want to start preparing Expensive2 when the browser is idle. This way it won’t block the initial render or cause jank, but by the time you click “next” Expensive2 might just already be complete, and in this case it’ll just replace the DOM node.

This is exactly what time slicing which I partially demoed in my talk will allow. Your code might look something like:

function Slideshow(props) {
  return (
    <div>
      <div hidden={props.current !== 0}>
        <Expensive1 />
      </div>
      <div hidden={props.current !== 1}>
        <Expensive2 />
      </div>
      <div hidden={props.current !== 2}>
        <Expensive3 />
      </div>
    </div>
  );
}

Note that hidden is a real HTML attribute. (It acts similar to display: none.) But it could also serve as a hint to React that the tree inside it doesn’t actually need to be committed immediately — because it’s not visible anyway. Also note: this is not a final API, I’m just explaining what it lets you do.

So when you render <Slideshow current={0} />, React mounts

<div>
  <div hidden={false}>
    <Expensive1 />
  </div>
  <div hidden={true}>
    <!-- not ready yet -->
  </div>
  <div hidden={true}>
    <!-- not ready yet -->
  </div>
</div>

and whenever the browser is idle (e.g. when you look at the first slideshow item), it will start working on Expensive2 in small chunks. When it’s ready, it will just append it to the hidden div — which won’t be observable to the user because it’s hidden.

<div>
  <div hidden={false}>
    <Expensive1 />
  </div>
  <div hidden={true}>
    <!-- not visible, but prepared during idle periods and now ready! -->
    <Expensive2 />
  </div>
  <div hidden={true}>
    <!-- not ready yet, but React will start working on it next -->
  </div>
</div>

Now if you switch to <Slideshow current={1} />, React doesn’t need to do any extra rendering because it has already “prepared” Expensive2 ahead of time.

And if you switch too fast, and React hasn’t been able to fully prepare Expensive2 yet, it will just pick it up where it left off but set a much more aggressive deadline since we want to see results within ~150ms.

I hope this explains it a bit! The crucial part here is time slicing. This optimization wouldn’t make sense if pre-rendering Expensive2 or Expensive3 blocked the thread since in this case it wouldn’t be worth slowing down the interactions while Expensive1 is visible. But thanks to React’s architecture, we can actually start pre-rendering Expensive2 and Expensive3 in small chunks without blocking the thread, and that’s what will enable this optimization.

20reactions
gaearoncommented, Aug 10, 2018

We’re going to have a first-class API for mounting invisible components asynchronously without blocking the thread in the future, right inside React. Stay tuned 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

Do you guys use lazy loading in Angular? Is ...
Yes and it is not just about JS file size. Different roles have different forms and consequently different JS files. It is more...
Read more >
An argument against lazy loading - Ben Smithett
For images that are content - like, say, an image surrounded by relevant words in a blog post - that blurry version is...
Read more >
Effects of Too Much Lazy Loading on Web Performance
Overusing lazy loading can affect the application performance negatively. So, in this article, I will discuss the performance effects of lazy loading to ......
Read more >
Lazy-Loading Images: How NOT to Really Annoy Your Users
If a user is not going to scroll past first 10 images then its is useless to load all of them. Let's say...
Read more >
programming practices - Is lazy loading always required?
However, as with the first example, the practicality of this, too, depends. If you do know that you have performance culprits that go...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found