JasonGenX
JasonGenX

Reputation: 5434

Systematic way to delay rendering until all resources are loaded

I use Webpack 4, Babel 7, React 16.8. My app loads google web fonts, external images required by many components taking part in the initial rendering when users load my pages.

I load fonts with a sass file like this:

@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,700');

I use images within all components like this:

import SearchSvg from '../../images/search_icon.svg';

and use them like this:

<img src={ SearchSvg } />

Now I know about <img onLoad=.....> and I know there are packages out there to test whether web fonts are already loaded. My question is: Is there any SYSTEMIC way/pattern to get the initial rendering of the React components wait until all those external resources are loaded?

Right now I use setTimeout with 500 ms to delay the root rendering in my index.js.

setTimeout(function() {
    render(
        ...
    );
}, 500);

I would LOVE to replace this hard-coded value with something that actually knows when everything's loaded -- Ideally without having to Add code in every single Component I use.

The motivation is of course to avoid Font/Image flickering when I initially render my app -- due to the rendering while images/fonts aren't fully loaded yet.

Upvotes: 5

Views: 3181

Answers (3)

Orkhan Huseynli
Orkhan Huseynli

Reputation: 1069

You may render your root component after onload event is fired.

The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets images. https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event

window.onload = function() {
   ReactDOM.render(<App/>, document.getElementById('root'));
};

If your purpose is to increase performance then I would highly recommend to consider Server Side Rendering.

Upvotes: 3

MahdiPOnline
MahdiPOnline

Reputation: 329

You can use a wrapper component to do all the checks and show a loading or nothing when that's happening and when everything is done, render the application. Something like:

class LoadApplication {
  state = { loaded: false }
  timer = null

  componentDidMount() {
    this.timer = setInterval(this.checkStuff, 50)
  }

  componentWillUnmount() {
    clearInterval(this.timer)
  }

  checkStuff() {
    if (stuffIsLoaded) {
      this.setState({ loaded: true })
    }
  }

  render() {
    return this.state.loaded ? this.props.children : null;
  }
}

...

ReactDOM.render(<LoadApplication><App /></LoadApplication>, document.querySelector('#root'))

This is kinda the same way CRA handles errors. The same way is recommended to catch errors in components in React's documentation so I think it might be what you're looking for. I hope it helps.

Upvotes: 1

alexr89
alexr89

Reputation: 410

In your case, you could use document.fonts.ready to check if the font is ready, and then conditionally render the parts you want once this is true

An example of this is found here:

https://developer.mozilla.org/en-US/docs/Web/API/Document/fonts

For your use case, you could use a similar function found at the above link, then set a state value to true if its ready. Then you could conditionally render what you want once this is true

For example:

Call the function in componentDidMount:

  componentDidMount() {
    this.isFontLoaded()
  }

The function uses document.fonts.ready, which returns a promise. We then set the state value fontReady to true once the promise returns:

  isFontLoaded = () => {
    document.fonts.ready.then(this.setState({ fontReady: true }))
  }

Only render the the things you want if fontReady is true:

{fontReady && <img src={ SearchSvg } />}

Upvotes: 2

Related Questions