Problem with Scope Functions in Method from Vue.js - javascript

I have a SetInterval inside to a Promise in Axios. When I try to execute a function in this SetInterval, I have the follow error:
methods: {
getJson() {
axios.post(url, FormObject, config)
.then(response => {
var searchId = JSON.stringify(response.data.searchId)
this.sendStatus(searchId)
var status = setInterval(function(){ this.sendStatus(searchId) },
30000);
})
.catch(err => (this.error = err))
},
sendStatus(searchId){},
}
The first call (this.sendStatus(searchId)) working correctly. However, the setInterval return this error:
Uncaught TypeError: this.sendStatus is not a function at eval

You are changing the context of this in your second call, as you are introducing a new function.
If you are using ES6, the easiest way to overcome this, is to use an arrow function instead of the function keyword.
var status = setInterval(() => { this.sendStatus(searchId) },
30000);
})
If you cannot use ES6, you have to use the .bind() function, which is explained in this question. Easier, but dirtier would be to reassign this to a local variable.
var that = this;
Then use that.sendStatus in your callback function.

You need to use an arrow function in your setInterval, like this :
setInterval(() => this.sendStatus(searchId))
Here is a resource explaining more the arrow functions and this

Related

Using clearInterval() inside Useeffect is not working. The function in setInterval() keeps getting called

Desired Outcome:
I am getting a script's status from an API and then checking what the status is.
If the status = 'XYZ', I need to call the getStatus function again (using setInterval),
else I just need to return/do nothing.
Issue:
The major issue is that clearInterval() is not working and the getStatus() function is getting called after the mentioned interval (10 seconds here) again and again, EVEN THOUGH the status IS NOT EQUAL to 'XYZ'.
Please help out. Thanks in advance.
const [intervalId, setIntervalId] = useState();
useEffect(() => {
getStatus();
}, []);
const getStatus = async () => {
await getScriptStatus()()
.then(response => {
if (response.data[0].status == 'XYZ') {
setIntervalId(
setInterval(() => {
getStatus();
}, 10000)
);
}
else
return () => clearInterval(intervalId);
})
.catch(error => {
errorMessage(error);
});
UPDATE:
Using React.useRef() instead of useState() for intervalId and clearing interval before using setInterval() in the 'if' condition worked or me.
Thank you for the helpful answers.
Use setTimeout instead of using setInterval.
For more details visit : Documentation on setTimeout
intervalId doesn't need to be a "state", rather store is as a ref and not a state.
Notice I changed your function from using .then().catch() to using async/await.
I think that the problem you're facing is caused because React State is asynchronous - the setIntervalId didn't update the intervalId yet, when you're trying to already access intervalId's new value
const intervalId = useRef(null);
const getStatus = async () => {
try {
const response = await getScriptStatus()()
if (response.data[0].status == 'XYZ') {
intervalId.current = setInterval(getStatus, 10000);
}
else return () => clearInterval(intervalId.current);
} catch (error) {
errorMessage(error);
}
}
Another suggestion:
The getStatus function should not act as a useEffect-function, because it's very confusing to see that getStatus returns a function.. and then you need to trail back to "AH it's a useEffect-function.. and the function returned is a cleanup function.. " - not good.. .
so.. My Suggested Options for that:
Either change the getState function to only getStatus.. and then when you call it from inside the useEffect- also handle the case in which you want to return a function (cleanup function)
Less suggested: Have all the content straight inside the useEffect. And since you want to use async/await -> create a self-calling-async-function: useEffect(()=>{ (async () => { await func(); })(); }, []);
Update:
In addition to all that ^
You really don't need the setInterval (like others have suggested). setTimeout will do just fine. But you might still want the clearTimeout (changed from clearInterval) if the functio might be called on different occasions
Main problem here is most probably that you are installing new intervals recursively: function installs interval that calls function that installs interval etc, effectively producing cascade of intervals. These are more or less in-revocable, because their IDs live ephemerally in function scope that installed them and are then thrown away.
You can observe it with slightly modified version of your code in this sandbox.
Change return () => clearInterval(intervalId); to this clearInterval(intervalId);.

JS: Rebound "this" in contextless function call

The function doSomethingElse in this example fails to execute since its this has been rebound to window or global (if in Node) due to a contextless call inside app.populateDatabase.
Is there any way to avoid this without referencing app inside every function?
loadDatabase function executes a callback according to a logic statement, if an imaginary database didn't exist, it populates it after loading, then the populateDatabase executes the callback it has been provided.
I cannot rebind the onLoaded argument to app since I don't know where it comes from, and the bind/apply/call abstraction overuse creates quite a mess.
var app = {};
app.loadDatabase = function(onLoaded) {
// If database already exists, only run a callback
var callback = onLoaded;
// If database doesn't exists, populate it, then run a callback.
if (!databaseExists) {
callback = this.populateDatabase.bind(this, onLoaded);
}
this.database = new sqlite.Database("file.db", function(error) {
if (error) { ... }
callback();
})
}
app.populateDatabase = function(onPopulated) {
// Contextless call here. <--------
onPopulated();
}
app.doSomethingElse = function() {
// this != app due to contextless call.
this.somethingElse();
}
app.run = function() {
// Load the database, then do something else.
this.loadDatabase(this.doSomethingElse);
}
app.run();
Just replace this.loadDatabase(this.doSomethingElse);
with this.loadDatabase(() => this.doSomethingElse());. This way you create a new arrow function but then doSomethingElse is called with the right this context.
You could also do .bind but I recommend the arrow function. Here with bind: this.loadDatabase(this.doSomethingElse.bind(this))
In general consider to move to promises & maybe async functions. Then do this:
this.loadDatabase().then(() => this.doSomethingElse());
or better with an async function:
await this.loadDatabase();
this.doSomethingElse();

how to call vue router from inside custom function

I use VueCLI and i do have such code inside methods:
submitCheck: function () {
function authUser() {
// returns a promise
}
function uploadFile() {
// also returns a promise
}
// ...
if ( error !== null ) {
EventBus.$emit('showError', error)
} else {
authUser()
.then(
function () {
return uploadFile();
})
.then(
function (data) {
EventBus.$emit('loaderStop')
this.$router.push('/awaiting');
})
.catch(function(error) {
EventBus.$emit('loaderStop')
console.log(error)
})
}
What i want to achieve is to route to /awaiting if all promises are resolved, but since i use this inside an anonymous function it doesnt have router. I am sure many coders met such a problem and needed to route from inside a function. How to do it?
Kalreg.
Multiple ways to handle this, I'd suggest you use arrow functions and learn about their differences to the other function style.
to be clear:
replace
function (data) {
EventBus.$emit('loaderStop')
this.$router.push('/awaiting');
}
with
data => {
EventBus.$emit('loaderStop');
this.$router.push('/awaiting');
}
You question context is not clear enough. If the code is executed in exponent methods, you can use arrow funciton like (agrs,...) => {...}. Otherwise, if this is not the case, you can use bind function like (function() {}).bind(this). Or just import $router in your code module.
Hope this can help you.
The answers from Sandro and Xhua are perfect. I just want to explain, WHY you get the error:
The problem is "this.". It refers to the parent object. So in your case "this." refers to the authUser Object and not to Vue. For your understanding: You could define "var that = this" outside of your authUser object and then use "that." inside. Or you go for the more sophisticated solutions.

Couldn't setState from the then function of the Promise

I'm trying to update the state from the promise which I received using the fetch function.
componentDidMount(){
fetch(url).then((responseText) => {
var response = responseText.json();
response.then(function(response){
this.setState(response);
});
});
}
I was getting the error that the setState is not an function
Then, I tried to bind(this) to pass the this value like below.
componentDidMount(){
fetch(url).then((responseText) => {
var response = responseText.json();
response.then(function(response){
this.setState(response);
});
}).bind(this);
}
It is not working now also. Same error again.
This is because of the scoping of this, so you're on to something when you're trying to use Function.prototype.bind. Your mistake is that you don't bind all the way down to the last anonymous function. What you probably want to do is use arrow functions all the way, like this:
componentDidMount(){
fetch(url)
.then((responseText) => responseText.json())
.then((response) => this.setState(response));
}
Arrow functions always keep the context of this.
Sorry, Just now found that I didn't bind the this variable properly.
Now, It is fixed.
componentDidMount(){
fetch(url).then((responseText) => {
const response = responseText.json();
response.then(function(response){
this.setState(response);
});
}.bind(this));
}
Your second promise doesn't have the current this context. You can use an arrow function here as well.
componentDidMount(){
fetch(url).then((responseText) => {
return responseText.json();
})
.then((response) => {
this.setState(response);
});
}
Also, chaining instead of nesting your promises will help with the legibility and might help you to avoid callback hell.
You also have the wrong method to setState it should look something like setState({name : 'string'})

ReactJS: setTimeout() not working?

Having this code in mind:
var Component = React.createClass({
getInitialState: function () {
return {position: 0};
},
componentDidMount: function () {
setTimeout(this.setState({position: 1}), 3000);
},
render: function () {
return (
<div className="component">
{this.state.position}
</div>
);
}
});
ReactDOM.render(
<Component />,
document.getElementById('main')
);
Isn't the state supposed to change only after 3 seconds? It's changing immediately.
My main goal here is to change the state every 3 seconds (with setInterval()), but since it was not working, I tried setTimeout(), which is not working either. Any lights on this? Thanks!
Do
setTimeout(
function() {
this.setState({ position: 1 });
}
.bind(this),
3000
);
Otherwise, you are passing the result of setState to setTimeout.
You can also use ES6 arrow functions to avoid the use of this keyword:
setTimeout(
() => this.setState({ position: 1 }),
3000
);
setTimeout(() => {
this.setState({ position: 1 });
}, 3000);
The above would also work because the ES6 arrow function does not change the context of this.
Anytime we create a timeout we should s clear it on componentWillUnmount, if it hasn't fired yet.
let myVar;
const Component = React.createClass({
getInitialState: function () {
return {position: 0};
},
componentDidMount: function () {
myVar = setTimeout(()=> this.setState({position: 1}), 3000)
},
componentWillUnmount: () => {
clearTimeout(myVar);
};
render: function () {
return (
<div className="component">
{this.state.position}
</div>
);
}
});
ReactDOM.render(
<Component />,
document.getElementById('main')
);
I know this is a little old, but is important to notice that React recomends to clear the interval when the component unmounts: https://reactjs.org/docs/state-and-lifecycle.html
So I like to add this answer to this discussion:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
setState is being invoked immediately due to the parenthesis! Wrap it in an anonymous function, then call it:
setTimeout(function() {
this.setState({position: 1})
}.bind(this), 3000);
You didn't tell who called setTimeout
Here how you call timeout without calling additional functions.
1. You can do this without making additional functions.
setTimeout(this.setState.bind(this, {position:1}), 3000);
Uses function.prototype.bind()
setTimeout takes the location of the function and keeps it in the context.
2. Another way to do the same even by writing even less code.
setTimeout(this.setState, 3000, {position:1});
Probably uses the same bind method at some point
The setTimeout only takes the location of the function and the function already has the context? Anyway, it works!
NOTE: These work with any function you use in js.
Your code scope (this) will be your window object, not your react component, and that is why setTimeout(this.setState({position: 1}), 3000) will crash this way.
That comes from javascript not React, it is js closure
So, in order to bind your current react component scope, do this:
setTimeout(function(){this.setState({position: 1})}.bind(this), 3000);
Or if your browser supports es6 or your projs has support to compile es6 to es5, try arrow function as well, as arrow func is to fix 'this' issue:
setTimeout(()=>this.setState({position: 1}), 3000);
There's a 3 ways to access the scope inside of the 'setTimeout' function
First,
const self = this
setTimeout(function() {
self.setState({position:1})
}, 3000)
Second is to use ES6 arrow function, cause arrow function didn't have itself scope(this)
setTimeout(()=> {
this.setState({position:1})
}, 3000)
Third one is to bind the scope inside of the function
setTimeout(function(){
this.setState({position:1})
}.bind(this), 3000)
You did syntax declaration error, use proper setTimeout declaration
message:() => {
setTimeout(() => {this.setState({opened:false})},3000);
return 'Thanks for your time, have a nice day 😊!
}
Just pass the function as a reference, no need to wrap it in an anonymous function or even bind it, which creates yet another function.
setTimeout(this.setState, 500, {position: 1});
Enter setTimeout
It seems people don't realise that setTimeout and setInterval actually accept optional unlimited parameters.
setTimeout(callback, timeout?, param1?, param2?, ...)
The reason is to make calling the callback simpler, so instead of this
setTimeout(
function(){
this.doSomething(true, "string", someVariable)
}.bind(this),
500
)
You can write this
setTimeout(this.doSomething, 500, true, "string", someVariable)
Isn't that beautiful and elegant? 😉
Bug?
There is no bug in React calling setTimeout instantly, so if you were puzzled by it, consider this.
function doSomething() {/* */}
const a = doSomething() // immediately invokes and assigns a result
const b = doSomething // stores a reference for later call
// call later
const x = a() // error "a is not a function"
const y = b() // invokes doSomething and assigns a result
And in your case with setState, this is basically the same thing.
When you register your setTimeout callback, you mistakenly immediately call it, where instead you should pass a reference to it.
function doSomething() {/* */}
const a = doSomething()
// wrong
setTimeout(doSomething(), 500) // This is basically the same as writing the `a` from above
setTimeout(a, 500) // like this. See the problem? a() cannot be called later.
To fix it, you have three options.
pass a reference
setTimeout(this.doSomething, 500)
wrap in an anonymous arrow function which is transparent to this,
meaning it captures the outer (parent) this.
note that this wraps your function in another function every time you call this
setTimeout(() => this.doSomething(), 500)
wrap in a standard anonymous function, but since it comes with it's own this, you must bind it to the this of the parent.
note that this wraps your function in another function AND THEN binds it, which creates yet another function. That is two additional functions every time you call it.
setTimeout(function(){this.doSomething()}.bind(this), 500)
useEffect(() => {
setTimeout(() => setActive(true), 5000);
},[]);
Try to use ES6 syntax of set timeout. Normal javascript setTimeout() won't work in react js
setTimeout(
() => this.setState({ position: 100 }),
5000
);

Categories