How to auto log off when a user is inactive in ReactJS? - 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

Related

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.

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.
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.

Method not being executed when leaving a component with componentWillUnmount()

I need to check between Facebook's expiration date and the current date when I enter in two different components. Dashboard and Pages. Both have this:
componentWillMount() {
const { linkedAccount, clearLinkedAccounts, getFacebookPages } = this.props
const now = moment().format('YYYY-MM-DD HH:mm:ss')
if (linkedAccount) {
if (now < linkedAccount.expiresIn) {
getFacebookPages()
} else {
clearLinkedAccounts()
}
}
}
componentWillUnmount() {
const { linkedAccount, clearLinkedAccounts } = this.props
const now = moment().format('YYYY-MM-DD HH:mm:ss')
if (linkedAccount && now > linkedAccount.expiresIn) {
clearLinkedAccounts()
}
}
But only when refreshing the page manually, the clearLinkedAccounts() is executed, by running componentWillMount, but before leaving it, it doesn't. I want it to be executed, if necessary, before entering in another route.
Refreshing the page is not like going from a page to another page inside the same single app context. React router allows you to go from a page to another within a React single app with tag like Link. It is why react router is so useful!
When you click on a link to go to another page with Link, the React single app is still active. As a result, componentWillUnmount wil lbe called because the single app is able to unmount component.
When you refresh the page (F5), a new page is loaded. As a result, the entire existing single app is removed and given to the browser garbage collector. In that case, you have no control on React component. So impossible to call componentWillUnmount because there is no more components at all.

onsen react navigator pushpage overwrites last page

In a project, my intention is to use only one Navigator element to handle all page navigation.
There is one singleton appState all over the code, and navigator is member of that, it gets initialized by the outermost App component.
ReactDOM.render(
<App/>,
document.getElementById('app')
);
The navigator is initiated by:
initialRoute = {
component: LoginPage,
props: {
}};
and
<Ons.Navigator
initialRoute={this.initialRoute}
renderPage={this.renderPage.bind(this)}
/>
and
renderPage(route: Route, navigator: typeof Ons.Navigator) {
const props: any = route.props || {};
if (appState.navigator == null) {
appState.navigator = navigator;
}
props.navigator = appState.navigator;
return React.createElement(route.component, route.props);
}
correctly with initialRoute. When I call pushPage(newRoute), the newRoute is apparently added, when checked at the time of addition. That is, I get the following right after pushing newRoute:
LoginPage
HomePage
However, a subsequent call to pushPage(someOtherRoute) yields
LoginPage
SomeOtherRouteComponent
I would expect
LoginPage
HomePage
SomeOtherRouteComponent
I have verified that there is no issue with synchronization etc, when I push the route object to an aside list, I get everything without any loss. But just pushPage is not working as I expect.
Any ideas, or missing something obvious? The snippets are TS.
It appears that any error during page load is caught by Navigator, and that causes the page to be not added into routes (but the page is still navigated to).
I have filed an issue on github with a workaround. An alternative workaround is to make sure that there are no JS errors during page load, which may not be 100% the case given 3rd party modules are present.

How to prevent route change using react-router

There's a certain page in my React app that I would like to prevent the user from leaving if the form is dirty.
In my react-routes, I am using the onLeave prop like this:
<Route path="dependent" component={DependentDetails} onLeave={checkForm}/>
And my onLeave is:
const checkForm = (nextState, replace, cb) => {
if (form.IsDirty) {
console.log('Leaving so soon?');
// I would like to stay on the same page somehow...
}
};
Is there a way to prevent the new route from firing and keep the user on the same page?
It is too late but according to the React Router Documentation you can use preventing transition with helping of <prompt> component.
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
if isBlocking equal to true it shows a message. for more information you can read the documentation.
I think the recommended approach has changed since Lazarev's answer, since his linked example is no longer currently in the examples folder. Instead, I think you should follow this example by defining:
componentWillMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
},
And then define routerWillLeave to be a function that returns a string which will appear in a confirmation alert.
UPDATE
The previous link is now outdated and unavailable. In newer versions of React Router it appears there is a new component Prompt that can be used to cancel/control navigation. See this example
react-router v6 no longer supports the Prompt component (they say that they hope to add it back once they have an acceptable implementation). However, react-router makes use of the history package which offers the following example for how to block transitions.
Note that to actually make this work in react router you have to replace the createBrowserHistory call with some hackery to make sure you are using the same history object as react router (see bottom of answer).
const history = createBrowserHistory();
let unblock = history.block((tx) => {
// Navigation was blocked! Let's show a confirmation dialog
// so the user can decide if they actually want to navigate
// away and discard changes they've made in the current page.
let url = tx.location.pathname;
if (window.confirm(`Are you sure you want to go to ${url}?`)) {
// Unblock the navigation.
unblock();
// Retry the transition.
tx.retry();
}
You'll need to put this inside the appropriate useEffect hook and build the rest of the functionality that would have otherwise been provided by prompt. Note that this will also produce an (uncustomizable) warning if the user tries to navigate away but closing the tab or refreshing the page indicating that unsaved work may not be saved.
Please read the linked page as there are some drawbacks to using this functionality. Specifically, it adds an event listener to the beforeunload event which makes the page ineligable for the bfcache in firefox (though the code attempts to deregister the handler if the navigation is cancelled I'm not sure this restores salvageable status) I presume it's these issues which caused react-router to disable the Prompt component.
WARING to access history in reactrouter 6 you need to follow something like the instructions here which is a bit of a hack. Initially, I assumed that you could just use createBrowserHistory to access the history object as that code is illustrated in the react router documentation but (a bit confusingly imo) it was intended only to illustrate the idea of what the history does.
We're using React Router V5, and our site needed a custom prompt message to show up, and this medium article helped me understand how that was possible
TLDR: the <Prompt/> component from react-router-dom can accept a function as the message prop, and if that function returns true you'll continue in the navigation, and if false the navigation will be blocked
React-router api provides a Transition object for such cases, you can create a hook in a willTransitionTo lifecycle method of the component, you are using. Something like (code taken from react-router examples on the github):
var Form = React.createClass({
mixins: [ Router.Navigation ],
statics: {
willTransitionFrom: function (transition, element) {
if (element.refs.userInput.getDOMNode().value !== '') {
if (!confirm('You have unsaved information, are you sure you want to leave this page?')) {
transition.abort();
}
}
}
},
handleSubmit: function (event) {
event.preventDefault();
this.refs.userInput.getDOMNode().value = '';
this.transitionTo('/');
},
render: function () {
return (
<div>
<form onSubmit={this.handleSubmit}>
<p>Click the dashboard link with text in the input.</p>
<input type="text" ref="userInput" defaultValue="ohai" />
<button type="submit">Go</button>
</form>
</div>
);
}
});

Resources