Reputation: 3
I'm writing a timer to auto-update the posted time. The code works well without this._timer, and the app crushed with "TypeError: Cannot read property 'comment' of undefined"
when I set the timer up.
I tried this._timer = setInterval(this._updateTimeString.bind(this), 3000);
instead of this._timer = setInterval(this._updateTimeString, 3000);
, it works but I don't know why.
componentWillMount() {
this._updateTimeString();
this._timer = setInterval(this._updateTimeString, 3000);
}
_updateTimeString() {
const now = Date.now();
const duration = (now - this.props.comment.createdTime) / 1000;
const timeDuration =
duration > 60
? duration >= 60 * 2
? duration > 60 * 60
? duration >= 60 * 60 * 2
? duration > 60 * 60 * 24
? duration > 60 * 60 * 24 * 2
? `${Math.round(duration / (60 * 60 * 24))} days ago`
: `${Math.round(duration / (60 * 60 * 24))} day ago`
: `${Math.round(duration / (60 * 60))} hours ago`
: `${Math.round(duration / (60 * 60))} hour ago`
: `${Math.round(duration / 60)} mins ago`
: `${Math.round(duration / 60)} min ago`
: `${Math.round(Math.max(duration, 1))} s ago`;
this.setState({
timeString: timeDuration
});
}
Upvotes: 0
Views: 253
Reputation: 84
You are calling out of context, use arrow function:
componentWillMount() {
this._updateTimeString();
this._timer = setInterval(() => this._updateTimeString(), 3000);
}
But first of all nested ternary is EVIL. Use something else. Simple if's or something like:
function formatTime(duration) {
switch(true) {
case duration > 60 * 60 * 24:
return 'days'
case duration > 60 * 60:
return 'hours'
case duration > 60:
return 'mins'
case duration > 1:
return 'secs'
default:
return ''
}
}
or whatever but not nested ternary.
Upvotes: 2
Reputation: 84
For your binding concerns, check this out:
new class A {
constructor() {
setInterval(this.tick, 1000)
setInterval(() => this.tick(), 1000)
}
tick() {
console.log(this)
}
}()
The difference is in context of invocations. Basically by passing this.tick
into setInterval
you pass a pointer to the function body itself not the pointer to object method and therefore inside tick()
this
points to where setInterval
is being executed which is Window
object.
Remember JS passes function parameters by value not by pointer - even if that parameter is function its being passed over by value i.e. function body is being copied not a pointer so this
will not get "copied" with it look:
let A = {
me: 'A',
whoAmI: function () { return `I am ${this.me}` }
}
console.log(A.whoAmI())
// prints "I am A"
// so method was invoked inside A context
// ( this points to A )
function someFunction( callback ) {
return callback()
}
console.log( someFunction(A.whoAmI) )
// "I am undefined"
// so method was invoked out of context
// ( this points to undefined )
// this is because argument passed a function body
// not a method pointer
// to simulate passing argument by pointer
// we have to pass another function that
// invokes A.whoAmI within A context
// we do that by defining new function that actually calls A.whoAmI()
// and pass it over
console.log( someFunction(() => A.whoAmI()) )
// "I am A"
A.whoAmI.bind(A)
is just another syntactic form of () => A.whoAmI()
Upvotes: 0
Reputation: 6790
this
isn't bound to the component instance in _updateTimeString
. In your constructor bind the method to the instance.
class MyComponent extends Component {
constructor(props) {
super(props);
this._updateTimeString = this._updateTimeString.bind(this);
}
}
And if you don't like the way that looks, here's some alternatives. Enjoy!
Upvotes: 1
Reputation: 31625
You can use .bind
but I recommend using arrow functions.
Just change:
_updateTimeString() {
To:
_updateTimeString = () => {
Upvotes: 0