Pretty new to React and ES6 conventions. I am trying to call an action from within a function that is inside of a componentWillMount(). This is resulting in an Uncaught TypeError: Cannot read property 'signoutUser' of undefined. Not quite sure how to resolve it, tried binding this, which did resolve the problem.
This my code in its current form:
// left_site.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { signoutUser } from '../actions/authentication';
export default function(ComposedComponent) {
class LeftSite extends Component {
constructor(props, context) {
super(props, context);
}
componentWillMount() {
var timerLeft = setInterval(timer, 1000);
function timer() {
if ((sessionStorage.getItem('timer') > 0) &&
(new Date().getTime() - sessionStorage.getItem('timer') > 5000)) {
this.props.signoutUser();
} else {
sessionStorage.setItem('timer', new Date().getTime());
}
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
return connect( null, { signoutUser })(LeftSite);
}
To explain what is going on, the company wants the user to automatically be logged out if they navigate away from any of the protected routes on the domain to another domain. One idea was to create a "heartbeat" that committed the time to sessionStorage every second so long as the user is on a protected route on the domain. The idea is if the navigate to another domain, then try to come back, if the difference in the last stored time and the current time is greater than 5000ms, it will automatically signout.
There may be a better way to do this, but I couldn't think of one that 1) didn't violate privacy or 2) wouldn't trigger the logout with a refresh, unbind for example.
The left_site.js is a HOC--I also have a required_login.js HOC to reroute to the login page if someone tries to access the protected route without authentication--so my protected routes are wrapped in component={LeftSite(RequireAuth(Home))}.
LeftSite is running fine, however when the conditional evaluates to true and it tries to trigger the this.props.signoutUser(); the error comes up.
Function timer is not bound to class. When it is executed at regular interval, the execution context changes. You have to bind the function before use. Also make sure you clear the interval at proper time, or when the component unmounts. I suggest you write this way
timer = () => {
if ((sessionStorage.getItem('timer') > 0) &&
(new Date().getTime() - sessionStorage.getItem('timer') > 5000)) {
this.props.signoutUser();
} else {
sessionStorage.setItem('timer', new Date().getTime());
}
}
componentWillMount() {
this.timerLeft = setInterval(this.timer, 1000)
}
componentWillUnmount() {
clearInterval(this.timerLeft);
}
You need to bind this to the timer function. The easier and recommended way to do this is by defining an arrow function for your timer, like this:
export default class MyComponent extends React.Component {
componentWillMount() {
const timer = () => {
console.log(this.props); // should be defined
};
const timerLeft = setInterval(timer, 1000);
}
}
This works because with arrow functions, this is set to the this of the enclosing context.
Related
I'm working on a React app, and I want to manage the user inactivity.
For it, I defined a countdown which is supposed to be reset to his original value if the user is doing something in the App.
The displayed components are rendered/surrounded by a Layout component.
My problem is that the layout is updated twice after every user action, since the .setState function is used in it. Therefore, the InactivityManager Component also is updated twice and the setInterval is executed twice at the same time.
I wrote a simple InactivityManager Component, which isn't rendering anything but is rendered in the Layout.
Here is the component:
import { Component } from 'react';
import { isLogged, logout } from '...';
class InactivityManager extends Component {
constructor(props) {
super(props);
this.refreshRemainingTime = this.refreshRemainingTime.bind(this);
}
componentDidUpdate() {
if (isLogged()) {
clearInterval(this.refreshRemainingTime);
localStorage.setItem('activityCountdown', '900');
window.setInterval(this.refreshRemainingTime, 5000);
}
}
refreshRemainingTime = () => {
let activityCountdown = parseInt(localStorage.getItem('activityCountdown'), 10);
activityCountdown -= 60;
localStorage.setItem('activityCountdown', activityCountdown);
if (activityCountdown <= 0) {
logout();
localStorage.removeItem('activityCountdown');
}
};
render() {
return null;
}
}
export default InactivityManager;
Any idea of what is the best approach? Also tried to include the Component in the App.js but the Component expect only one "Route" child.
Thanks!
Can you modify the componentDidUpdate method a little bit.
componentDidUpdate() {
if (isLogged()) {
if (this.interval) clearInterval(this.interval);
localStorage.setItem('activityCountdown', '900');
this.interval = setInterval(this.refreshRemainingTime, 5000);
}
}
I finally prefered to use setTimeout instead of setInterval, there is no problem anymore with this.
Thanks!
So here is the function
greenToAmber() {
let x = 0;
setInterval(function () {
x++;
..... Rest of code
}, 500);
}
}
I've set this component up using the routes as you would expect, I've tried calling the function in OnInit as well, but every time I go this component then off it and back again the counter will launch a second instance of the counter & then a third ect for each time I leave & come back to the page.
From my understanding I thought ngOnDestroy was meant to prevent this, I'm assuming that I'll need to subscribe and then unsubscribe to the function maybe on destroy?
But I'm relatively new to angular 4 so pretty inexperienced.
setInterval is not destroyed on component destroy, you have to save the interval id in your class and use clearInterval javascript native function to clean it on your component destroy hook ngOnDestroy:
import {Component, OnDestroy} from '#angular/core';
#Component({ ... })
export class YourClass implements OnDestroy {
public intervalId: any;
public greenToAmber() {
let x = 0;
// registering interval
this.intervalId = setInterval(function () {
// ..... Rest of code
}, 500);
}
}
public ngOnDestroy () {
if (this.intervalId !== undefined) {
clearInterval(this.intervalId); // cleaning interval
}
}
}
Hopes it helps.
You're setting a demon process with setInterval. The behavior you mention is expected. That is how JavaScript works. Its not Angular specific.
SetInterval always returns a ID which you might want to track in your controller. When you want to destroy it, make sure you do it specifically.
eg:
greenToAmber() {
let x = 0;
$scope.myDemon = setInterval(function () {
x++;
..... Rest of code
}, 500);
}
}
//Somewhere else; where you want to destroy the interval/stop the interval:
If($scope.myDemon) {
clearInterval($scope.myDemon);
}
I have a React component with a method:
class Timer extends Component{
start(){
this.timerInterval=setInterval(this.tick,1000);
}
[...]
}
I want to be able to call the method start() whenever the user presses a combination of keys.
In my main.js file I have:
app.on('ready', function(){
createWindow();
globalShortcut('Cmd+Alt+K',function(){
//call start() here
})
});
How can I achieve this? There's not much information I could find on this subject.
When in need of use of electron library inside react you should import it using electron remote
const { globalShortcut } = require('electron').remote;
class Timer extends Component{
componentDidMount = () => {
globalShortcut.register('Cmd+Alt+K', () => {
//your call
this.start()
});
}
start(){
this.timerInterval=setInterval(this.tick,1000);
}
[...]
}
I was hoping someone with Electron-specific experience would address this, but it's been a full day now.
To cross the outside world / React component barrier, you're probably best off using a custom event. To do that, you'll need access to the DOM element created by React in response to your render call, then listen for your custom event directly on the element. Here's an example, see the comments for details. Note that in this example I'm passing an object as the event detail; that's just to show you can do that (not just simple strings), but you could just do {detail: "Tick #" + ticker} instead and use event.detail directly as the message.
class Example extends React.Component {
// Normal component setup
constructor(...args) {
super(...args);
this.state = {message: ""};
// Our event handler. (We could also define this at the class level using the class
// fields syntax that's currently at Stage 3 in the ECMA TC39 process; most React
// setups with JSX transpile class fields syntax.)
this.doSomething = event => {
this.setState({message: event.detail.message || ""});
};
}
// Render with a way of finding the element in the DOM from outside (in this case
// I'm using a class, but it could be an ID, or just knowing where it is in the DOM).
// We use a ref so we can hook the event when the elemet is created.
render() {
return <div className="foo" ref={element => this.element = element}>{this.state.message}</div>;
}
// Hook the event on mount, unhook it on unmount
componentDidMount() {
this.element.addEventListener("my-event", this.doSomething);
}
componentWillUnmount() {
this.element.removeEventListener("my-event", this.doSomething);
}
}
// Top-level app rendering
ReactDOM.render(<Example />, document.getElementById("root"));
// Demonstrate triggering the event, in our case we do it every half-second or so
let ticker = 0;
setInterval(() => {
const foo = document.querySelector("#root .foo");
++ticker;
foo.dispatchEvent(new CustomEvent("my-event", {detail: {message: "Tick #" + ticker}}));
}, 500);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
In my web application, I want to prompt user when he/she tries to close the browser/tab based upon Redux state using event handlers.
I am using the below code to prompt user before exiting based upon 'isLeaving' state.
function mapStateToProps(state) {
const {isLeaving} = state.app.getIn(['abc']);
return {
isLeaving
};
}
#connect(mapStateToProps, {}, undefined, {withRef: true})
export default class MyClass extends React.component {
#autobind
stayOnPage(event) {
if (this.props.isLeaving) {
const message = 'Are you sure you want to leave';
event.returnValue = message;
return message;
}
return false;
}
componentDidMount() {
window.addEventListener('beforeunload', (event) => {
this.stayOnPage(event);
});
}
componentWillUnmount() {
window.removeEventListener('beforeunload', (event) => {
this.stayOnPage(event);
});
}
componentWillReceiveProps(nextProps) {
if (this.props.prop1 !== nextProps.prop2) {
// do something
}
}
render() {
//
}
}
This code works fine. But whenever there is a change in prop1, I see that this.props.isLeaving does not have updated value.
Can somebody help? What is I'm doing wrong here?
You aren't cleaning up correctly in componentWillUnmount. The event handler you're trying to remove is a brand new function closure, not the same instance that you added. You should just attach the actual handler, rather than using an arrow function, like so:
componentDidMount() {
window.addEventListener('beforeunload', this.stayOnPage);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.stayOnPage);
}
Possibly what you are seeing is the event triggering on a stale component instance, so it has old state.
React uses synthetic events, basically events are recycled to be more performant. You can read more on that here
What I normally do is pass the value I need on invocation
Not sure if this will work in your specific case because I define my event handlers in JSX instead of accessing the window object (which you might want to do as well but to be honest I'm not sure) but I use this pattern all the time to handle e.target.value properly
I'm trying to create a basic "Toast" like service in my React app using Alt.
I've got most of the logic working, I can add new items to the array which appear on my view when triggering the add(options) action, however I'm trying to also allow a timeout to be sent and remove a toast item after it's up:
onAdd(options) {
this.toasts.push(options);
const key = this.toasts.length - 1;
if (options.timeout) {
options.timeout = window.setTimeout(() => {
this.toasts.splice(key, 1);
}, options.timeout);
}
}
On add, the toast appears on my page, and the timeout also gets triggered (say after a couple of seconds), however manipulating this.toasts inside of this setTimeout does not seem to have any effect.
Obviously this is missing the core functionality, but everything works apart from the setTimeout section.
It seems that the timeout is setting the state internally and is not broadcasting a change event. It might be as simple as calling forceUpdate(). But the pattern I use is to call setState() which is what I think you might want in this case.
Here is an example updating state and broadcasting the change event.
import alt from '../alt'
import React from 'react/addons'
import ToastActions from '../actions/ToastActions'
class ToastStore {
constructor() {
this.toasts = [];
this.bindAction(ToastActions.ADD, this.add);
this.bindAction(ToastActions.REMOVE, this.remove);
}
add(options) {
this.toasts.push(options);
this.setState({toasts: this.toasts});
if (options.timeout) {
// queue the removal of this options
ToastActions.remove.defer(options);
}
}
remove(options) {
const removeOptions = () => {
const toasts = this.toasts.filter(t => t !== options);
this.setState({toasts: toasts});
};
if (options.timeout) {
setTimeout(removeOptions, options.timeout);
} else {
removeOptions();
}
}
}
module.exports = alt.createStore(ToastStore, 'ToastStore');