Systematic way to delay rendering until all resources are loaded - reactjs

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.

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.

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 } />}

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.

Related

Display a loading text/spinner until a component is fully loaded(text,images...etc) in React

I searched for a solution like this but I just couldn't find it properly so I am asking this question if anyone can help.
This is a Sandbox that I made and the idea is something like this: I want to show a loading text until my component(let's say a whole website: including images, text.. etc..) is fully loaded, tried to make this with useEffect hook but the useEffect only shows the loading text until the component is mounted, and the hook does that, but there are things like images inside this component that aren't fully loaded yet, so this does not work the way I want. I would appreciate it if anyone knows a way to make this work, there is a lot of information on the web for making something like this with a setTimeout but I think this is a bit of a tricky/fake way to do it because the loading spinner should show depending on the speed of your ISP right? so for users with a better internet connection the loading text/spinner time will be shorter and for others, it will be longer.
Link to Sandbox
you can use the following function on your component :
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
console.log("do something");
}
}
or controll all document's loading period using
switch (document.readyState) {
case "loading":
// The document is still loading.
break;
case "interactive":
// The document has finished loading. We can now access the DOM elements.
// But sub-resources such as scripts, images, stylesheets and frames are still loading.
console.log('do something')
break;
case "complete":
// The page is fully loaded.
console.log("do something");
break;
}
you can also use this function if you need it :
window.addEventListener("load", event => {
var image = document.querySelector('img');
var isLoaded = image.complete && image.naturalHeight !== 0;
console.log(isLoaded);
});
You could have a 'loading' state that is true at first and then change it's value with the onload function.
You could use it like this:
<div onload={setLoading(false)}>
<img src="..." />
<img src="..." />
<img src="..." />
</div>
Onload executes when all of it's children content has finished loading. You could read more about this at https://www.w3schools.com/tags/ev_onload.asp

How to refresh the facebook like url in reactjs (using facebook-sdk CDN)

I am using Facebook's like button as generated by facebook's like button configurator. However in order to get facebook-sdk to finish loading before the Like button, I had to use something called react-load-script and make a my own wrapper component for the like button html I got from the configurator.
my like button
class Like extends React.Component {
state = {
facebookLoaded: false
};
handleFacebookLoaded = () => this.setState({
facebookLoaded: true
});
FacebookSDK = () => <>
<div id="fb-root"></div>
<Script
async defer crossOrigin="anonymous"
url="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v3.3&appId=391623981325884&autoLogAppEvents=1"
onLoad={this.handleFacebookLoaded}
/>
</>;
render() {
return <>
<this.FacebookSDK />
{this.state.facebookLoaded
? <div class="fb-like" data-href={this.props.url} data-width="" data-layout="button_count" data-action="like" data-size="large" data-show-faces="true" data-share="true" />
: null}
</>;
}
}
In my code all the script loading stuff actually happens in App.jsx, but I moved it into one class just to show a simple version.
This part seems to work fine, the issue lies when changing the url passed to data-href.
I checked the react dom in the browser and the data-href is actually being updated properly, however this does not affect the actual url that is being used by the like button, unless I do a full page refresh. I'm assuming this has to do with how the data-href is being used by facebook-sdk. (edit: after testing I'm not sure anymore)
I've found many questions about this on Stack Overflow, however none of them seem to be based off the CDN version of facebook buttons
From what I understand, the div containing the href needs to be placed out and back into the DOM in order for the facebook-sdk to detect a change, but I don't know how to do this in react without a full page refresh. Also I'm not certain this is even the right solution.
-- Update --
I just noticed something else that seems like useful information. If I navigate to the page with the like button, then it doesn't show up. It will only show up if the page refreshes. I tested it by moving the part that loads the script into the like component (like in the example shown above) and that didn't change the behavior at all.
-- more experimenting --
I wrote an event handler that takes all the facebook related jsx out of the dom and back in (by toggling a button) However when all the code goes back into the dom (both jsx and html), the UI for the button does not come back. I'm really now sure how this is possible as I'm literally reloading the script and everything facebook related so this should be equivalent to a page refresh no?
I fixed the issue thanks to misorude. The part I was missing was calling window.FB.XFBML.parse(). I didn't realize I could access FB the same way using the CDN. If anyone is looking for a react solution here is the working code:
class Like extends React.Component {
constructor(props) {
super(props);
this.state = {
url: props.url,
}
}
handleChangePage() {
let likeBtn = document.createElement('div');
likeBtn.className = "fb-like";
likeBtn.setAttribute("data-href", this.props.url);
likeBtn.setAttribute("data-width", "");
likeBtn.setAttribute("data-layout", "button_count");
likeBtn.setAttribute("data-action", "like");
likeBtn.setAttribute("data-size", "large");
likeBtn.setAttribute("data-show-faces", "true");
likeBtn.setAttribute("data-share", "true");
let likePanel = document.getElementById("like-panel");
likePanel.removeChild(likePanel.childNodes[0]);
likePanel.appendChild(likeBtn);
window.FB.XFBML.parse(likePanel)
this.setState({ url: this.props.url });
}
componentDidMount() {
this.handleChangePage();
}
render() {
if(this.props.url !== this.state.url)
this.handleChangePage();
return <div id="like-panel">
{this.props.facebookLoaded
? <div className="fb-like" data-href={this.props.url} data-width="" data-layout="button_count" data-action="like" data-size="large" data-show-faces="true" data-share="true" />
: null}
</div>;
}
}
I moved the CDN out of this component so that it only loads the sdk once for the whole app.

Will ReactDOM.hydrate() trigger lifecycle methods on the client?

From the React 16 docs about ReactDOM.hydrate(),
Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Will ReactDOM.hydrate() also trigger lifecycle methods on the client such as componentWillMount(), componentDidMount() during initial render?
Will render() method be called on the client during hydration? I suppose not, because that's the difference between ReactDOM.render() and ReactDOM.hydrate()?
If render method won't be called on the client, we wouldn't expect componentDidMount() lifecycle method to be triggered.
If none of the lifecycle methods are called on the client, how would we know when has React finished rendering. I suppose the callback in the following syntax:
ReactDOM.hydrate(element, container[, callback])
I want to understand if there are lifecycle methods / hooks (which give more control over the application) available when React is "attempting to attach event listeners to existing markup".
Since ReactDOM.hydrate is (and should be) called on the client then YES it is supposed to run componentDidMount. componentWillMount is already called when rendered on the server.
componentDidMount does not run on the server, therefore when you call hydrate, the app runs the event.
Think about hydrate as a different render method. It does render but not in the same way. It looks for mismatches between your server rendered React and your client React. It does not render everything again.
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content (such as timestamps), but you should treat mismatches as bugs and fix them
However you might want to do some crazy stuff like rendering something completely different on the client side (than what was rendered on the server). For this pay attention to this paragraph
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.
So as you can see it does a render pass. If there are no mismatches React is optimized for that.
I hope it was clarifying. I speak from experience with React SSR and basic understanding of reading the docs.
The rendered elements probably aren't same between server and client, because initially the elements are rendered into texts at the server in memory, therefore they are not mounted. When the content is moved to client, it can be re-attached to react via hydrate which is fake "render" to wire with the rest of react functionalities, such as events.
In order to tell when it's hydated, here's a piece from internet which I found clearly stated the above rational. https://dev.to/merri/understanding-react-ssr-spa-hydration-1hcf?signin=true
const HydrateContext = createContext('hydrated')
export function useIsHydrated() {
return useContext(HydrateContext)
}
export function IsHydratedProvider({ children }) {
const [isHydrated, setIsHydrated] = useState(false)
useEffect(() => {
setIsHydrated(true)
}, [])
return (
<HydrateContext.Provider value={isHydrated}>
{children}
</HydrateContext.Provider>
)
}
To use it,
function MyComponent() {
const isHydrated = useIsHydrated()
return !isHydrated ? 'Initial render' : 'SPA mode'
}
function App() {
return (
<IsHydratedProvider>
<MyComponent />
</IsHydratedProvider>
)
}
It feels to me, any rendered component teleports from the server to the client.
p.s Here's another article which talks about the second render after the mount, https://medium.com/swlh/how-to-use-useeffect-on-server-side-654932c51b13
I read the type of ReactDOM.hydrate in TypeScript system:
(
element: SFCElement<any> | Array<SFCElement<any>>,
container: Container| null,
callback?: () => void
): void;
And example to the above declaration:
ReactDOM.hydrate(
<App />, // element
document.getElementById('root'), // container
() => { // callback
/* do what you want after hydration */
}
);

How to auto log off when a user is inactive in ReactJS?

I need to know if there is any API in REACT JS or HTML5 which provides the functionality of auto-log off when a user is inactive. My code is below I don't know what is wrong it is giving me the error startTimer is not defined and unnecessary binding. Please tell me where I am going wrong
import React from 'react';
import ReactDOM from 'react-dom';
import './App.css';
class Toogle extends React.Component {
constructor(props) {
super(props);
//step 1
this.handleClick = this.handleClick.bind(this);
this.startTimer=this.startTimer.bind(this)
}
handleClick() {
console.log('Button is clicked');
// clearTimeout(timeoutTimer);
startTimer() {
var timeoutTimer = setTimeout(function() {
window.location.href = '#/security/logout';
}.bind(this), 1000);
}
}
render() {
return (
<div onMouseMove={this.handleClick}>
Move the cursor over me
</div>
);
}
}
ReactDOM.render(
<Toogle />,
document.getElementById('root')
);
Accepted answer may do the job for you , but the time between navigating routes is not the real "in-active" time.
react-idle can do this , read the documentations and you can do it the way it should be.
If you are using the react-router library to add front-end route configuration, you can fire a function whenever a user navigates to another page.
<Route path="/" onEnter={onUserNavigate} onChange={onUserNavigate}>
...
</Route>
Then you can have your event handler function calculate the time between to user navigations.
N.B. This is only a skeleton code (serves as a prototype for your actual method)
function onUserNavigate() {
let idleTime = getCurrentTime() - getPreviousNavTime();
storeCurrentNavTime();
if (idleTime > ALLOWED_IDLE_TIME)
window.location.href = '#/security/logout';
}
So, the above function assumes that,
You have a method to get the current time (getCurrentTime function)
You have a method to store and get timestamps when a user navigates to a page
You have a constant called ALLOWED_IDLE_TIME to store the minimum allowed idle time the user spends between two pages.
You have a logout URL (valued #/security/logout in the example code above)
The user isn't expected to interact with the page without navigating through the react-router paths. For example, the user may interact with the page using a browser console beyond the allowed idle time.
For timestamp related calculations and methods you can use any JavaScript library like momentjs
Hope that helped.
UPDATE There is an unnecessary binding around the part }.bind(this), 1000); Just remove the .bind(this) segment.
Plus, the startTimer() { fragment should give you syntax error. For that you should remove the startTimer() { line and its corresponding closing } line

React.js shouldComponentUpdate() and react-router Link

I currently have a doubt about the correct combined implementation of react-router Link navigation and shouldComponentUpdate() on the root application level.
That is, I have a root component called App.jsx which contains a global component with a header, footer, sidebar etc and this same component has an ajax long-poll which retrieves new registrations in the system and updates the state when new users register.
Since I don't want to push a re-render to the component (and therefore all it's children) on ajax responses that don't have updates I decided to make use of the lovely shouldComponentUpdate() method.
So, I came up with something like this - noting that I'm making use of lo-dash:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState);
}
With this the component correctly ignores irrelevant responses about the latest registrations.
Now, the problem appears when I have to make the routing. To clarify before, this is the kind of structure of the render():
Note: the _routerTransitionKey is just a helper I have to not make transitions when I'm navigating internal views state and it's working correctly.
<Grid key='app' id="wrapper" className="no-padding">
<Header user={this.state.user} allRegistrations={this.state.allRegistrations}/>
<section id="page-wrapper">
<NotificationArea key='internalNotification' />
<RouteHandler key={_routerTransitionKey} user={this.state.user} allRegistrations={this.state.allRegistrations}/>
</section>
</Grid>
Because I have the RouteHandler inside this global component, I have the issue that a change in the route is completely ignored by it, since the application state itself didn't change. That causes the component to never trigger the render() on navigation and therefore never update the RouteHandler.
What I needed would be something like:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState) || ROUTE_CHANGED ;
}
My question is: does anybody out there knows of a clever approach to this issue? I'm trying to avoid having to create yet another wrapping component to handle the Routes before they reach this App component I currently have...
So, after the tip from #WayneC, even though the react-router doesn't inject the props directly into the react component props, there's a possible way inspired by that approach.
I achieved what I wanted by doing a slight change using not the this.props, but instead the this.context.router.getCurrentPath()
So now the solution looks like this:
shouldComponentUpdate (/*nextProps*/, nextState) {
return !_.isEqual(this.state,nextState) || this.context.router.getCurrentPath() !== _routerTransitionKey;
}
Just to make it clearer, my _routerTransitionKey gets its value from an imported Util that looks mostly like this:
var Utils = {
Router: {
TransitionKey: {
get: function(){
return Router.HistoryLocation.getCurrentPath();
}
}
}
}
_routerTransitionKey = Utils.Router.TransitionKey.get();
This _routerTransitionKey is scoped in an upper level, and I modify it on every render(), so that I keep track of it for later comparison.
And... that's it.

Resources