Function to update state:
animate() {
setInterval(function(){
setTimeout( this.setState({
a: '123'
}), 1000);
}, 4000);
}
The method called:
componentDidMount() {
this.animate();
}
Error:
Uncaught TypeError: this.setState is not a function
Then the next code tried:
animate() {
setInterval(function(){
setTimeout( () => {this.setState({
a: '123'
})}, 1000);
}, 4000);
}
And the next error was:
Uncaught TypeError: _this2.setState is not a function
The problem stems from your definition of setInterval.
When you do setInterval(function() {...}), the this keyword is no longer bound to the React component but to the function it is called within because of the introduction of the new scope.
You can either switch it to:
animate() {
const self = this
setInterval(function() {
setTimeout(() => self.setState({ a: '123' }), 1000)
}, 4000)
}
This way self is assign to the React component value of this before the new scope is introduced, or you can use all arrow functions to preserve the this keyword referencing the component:
animate() {
setInterval(() => {
setTimeout(() => this.setState({ a: '123' }), 1000)
}, 4000)
}
Although the answer submitted by m_callens is the proper one, I'll add this possibility using bind as well, for those who use pre-ES6 JavaScript.
animate() {
setInterval((function(){
setTimeout((function() {this.setState({
a: '123'
})}).bind(this), 1000);
}).bind(this), 4000);
}
Related
I have this handle change function. I cannot figure out why in the timeout section, I cannot access this.state? It works if I do it outside of that?
The part where i'm trying to setState employment_level gives me an 'undefined' error.
The console log console.log(${targetName}: ${targetValue}); works perfecly... but the two that reference this.state.empoloyee and this.state.employment_level do not.
handleChange = e => {
const targetName = e.target.name;
const targetValue = e.target.value;
this.setState({
[targetName]: targetValue,
});
if (this.state.editingTimeout) {
clearTimeout(this.state.editingTimeout);
}
this.setState({
editing: false,
editingTimeout: setTimeout(function () {
console.log(`${targetName}: ${targetValue}`);
this.setState({
employment_level: targetValue,
});
console.log(this.state.employee);
console.log(this.state.employment_level);
}, 300),
});
}
Maybe it works if you use an arrow functions in:
editingTimeout: setTimeout(() =>
I want to get the state inside of setTimeOut
change = () => {
setTimeout(function() {
this.setState({
categories: [...this.state.categories, { time: 20, msg: "msg1", visible:true }]
}) }, 3000);
};
in this this.state.categories i get the next error: "Cannot read property 'categories' of undefined"
That's because the callback you are providing to setTimeout is using the function keyword, which will have its own scope unless you bind it manually. You can fix that cleanly by making the callback an arrow function:
change = () => {
setTimeout(() => {
this.setState({
categories: [...this.state.categories, { time: 20, msg: "msg1", visible:true }]
})
}, 3000);
};
Based on your other comment, if you'd like to manually bind it instead, you can do this:
change = () => {
setTimeout(function () {
this.setState({
categories: [...this.state.categories, { time: 20, msg: "msg1", visible:true }]
})
}.bind(this), 3000);
};
Use arrow function, you missed the this binding. Arrow function will do this for you implicitly.
change = () => {
setTimeout(() => {
this.setState({
categories: [...this.state.categories, { time: 20, msg: "msg1", visible:true }]
}) }, 3000);
};
I'm now to react and I'm wondering if what I've done is a bad way of creating this component. What I wonder is:
Is this the correct way to do the callback in the setState? If not, where should this line $('#editor').data('kendoEditor').value(data) be placed?
componentDidUpdate(prevProps) {
if(this.props.id!== prevProps.id) {
$.get('/webapi/GetData?id=' + this.props.id, function (data) {
this.setState({ editorValue: data }, $('#editor').data('kendoEditor').value(data));
}.bind(this));
}
}
Why doesn't this work?
componentDidMount() {
this.initEditor();
$.get('/webapi/GetData', function (data) {
this.setState({ data: data });
}.bind(this));
}
initEditor = () => {
$("#editor").kendoEditor({
value: this.state.editorValue,
)}
}
but this works?
componentDidMount() {
$.get('/webapi/GetData', function (data) {
this.setState({ data: data });
this.initEditor();
}.bind(this));
}
To properly do a callback after setState follow this format:
this.setState( { foo: bar }, () => callbackFunction() )
EDIT
To answer the second part of your question, you shouldn't need to use those lines of code at all. Let React handle the rendering. Say you have a render like so
render() {
return(
<div>
<input type="text" name="someValue" data-kendoeditor={this.state.editorValue} />
</div>
)
}
Then call setState like:
componentDidUpdate(prevProps) {
if(this.props.id!== prevProps.id) {
$.get('/webapi/GetData?id=' + this.props.id, function (data) {
this.setState({ editorValue: data });
}.bind(this));
}
}
This will rerender the value from state to the DOM.
I have a print function, it first sets the state of isPrinting to true and open a pring dialog. Once the dialog is being closed it sets the state of isPrinting to false and at this point I'm getting the error (second setState):
Uncaught ReferenceError: setState is not defined
I binded function to current context with the arrow function.
handlePrint = () => {
this.setState({ isPrinting: true }, () => { //setState is working working properly
window.print();
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener = (mql) => {
if (!mql.matches) {
this.setState({ isPrinting: false }); //Error
}
}
}
});
};
I am not sure what you are trying to achieve here but the window.print() freezes the app. There is no code being run unless someone clicks the printing screen away. I works just like window.alert("..."). You can try that by printing a thimestamp right after the win.print. So besides that there is a problem with the this context that cannot be reached the whole function is useless. Because you could just do:
handlePrint = () => {
this.setState({ isPrinting: true }, () => {
window.print() //freezes until someone clicks it away.
this.setState({ isPrinting: false }) //Error
})
}
Regards
Try this.
handlePrint = () => {
let _this = this;
this.setState({ isPrinting: true }, () => {
window.print();
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener = (mql) => {
if (!mql.matches) {
_this.setState({ isPrinting: false });
}
}
}
});
};
This should help
handlePrint = () => {
this.setState({ isPrinting: true }, () => { //setState is working working properly
window.print();
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener = (mql) => {
if (!mql.matches) {
return { isPrinting: false };
}
}
}
});
};
setState method should return a new state rather than try to execute anything.
At the second time, instead of setState, just return the new state like:
return {
isPrinting: false,
};
How did you use the function 'mediaQueryList.addListener'?You can console the two 'this' and see if they are same.
I am having a ReactJS component which does two things:
- on ComponentDidMount it will retrieve a list of entries
- on Button click it will submit the select entry to a backend
The problem is that i need to mock both requests (made with fetch) in order to test it properly. In my current testcase i want to test a failure in the submit on the button click. However due some odd reason the setState is triggered however the update from that is received after i want to compare it.
Dumps i did for the test. First one is the state as listen in the test. The second is from the code itself where it is setting state().error to the error received from the call
FAIL react/src/components/Authentication/DealerSelection.test.jsx (6.689s)
● Console
console.log react/src/components/Authentication/DealerSelection.test.jsx:114
{ loading: true,
error: null,
options: [ { key: 22, value: 22, text: 'Stationstraat 5' } ] }
console.log react/src/components/Authentication/DealerSelection.jsx:52
set error to: my error
The actual test code:
it('throws error message when dealer submit fails', done => {
const mockComponentDidMount = Promise.resolve(
new Response(JSON.stringify({"data":[{"key":22,"value":"Stationstraat 5"}],"default":22}), {
status: 200,
headers: { 'content-type': 'application/json' }
})
);
const mockButtonClickFetchError = Promise.reject(new Error('my error'));
jest.spyOn(global, 'fetch').mockImplementation(() => mockComponentDidMount);
const element = mount(<DealerSelection />);
process.nextTick(() => {
jest.spyOn(global, 'fetch').mockImplementation(() => mockButtonClickFetchError);
const button = element.find('button');
button.simulate('click');
process.nextTick(() => {
console.log(element.state()); // state.error null even though it is set with setState but arrives just after this log statement
global.fetch.mockClear();
done();
});
});
});
This is the component that i actually use:
import React, { Component } from 'react';
import { Form, Header, Select, Button, Banner } from '#omnius/react-ui-elements';
import ClientError from '../../Error/ClientError';
import { fetchBackend } from './service';
import 'whatwg-fetch';
import './DealerSelection.scss';
class DealerSelection extends Component {
state = {
loading: true,
error: null,
dealer: '',
options: []
}
componentDidMount() {
document.title = "Select dealer";
fetchBackend(
'/agent/account/dealerlist',
{},
this.onDealerListSuccessHandler,
this.onFetchErrorHandler
);
}
onDealerListSuccessHandler = json => {
const options = json.data.map((item) => {
return {
key: item.key,
value: item.key,
text: item.value
};
});
this.setState({
loading: false,
options,
dealer: json.default
});
}
onFetchErrorHandler = err => {
if (err instanceof ClientError) {
err.response.json().then(data => {
this.setState({
error: data.error,
loading: false
});
});
} else {
console.log('set error to', err.message);
this.setState({
error: err.message,
loading: false
});
}
}
onSubmitHandler = () => {
const { dealer } = this.state;
this.setState({
loading: true,
error: null
});
fetchBackend(
'/agent/account/dealerPost',
{
dealer
},
this.onDealerSelectSuccessHandler,
this.onFetchErrorHandler
);
}
onDealerSelectSuccessHandler = json => {
if (!json.error) {
window.location = json.redirect; // Refresh to return back to MVC
}
this.setState({
error: json.error
});
}
onChangeHandler = (event, key) => {
this.setState({
dealer: event.target.value
});
}
render() {
const { loading, error, dealer, options } = this.state;
const errorBanner = error ? <Banner type='error' text={error} /> : null;
return (
<div className='dealerselection'>
<Form>
<Header as="h1">Dealer selection</Header>
{ errorBanner }
<Select
label='My dealer'
fluid
defaultValue={dealer}
onChange={this.onChangeHandler}
maxHeight={5}
options={options}
/>
<Button
primary
fluid
onClick={this.onSubmitHandler}
loading={loading}
>Select dealer</Button>
</Form>
</div>
);
}
}
export default DealerSelection;
Interesting, this one took a little while to chase down.
Relevant parts from the Node.js doc on Event Loop, Timers, and process.nextTick():
process.nextTick() is not technically part of the event loop. Instead, the nextTickQueue will be processed after the current operation is completed, regardless of the current phase of the event loop.
...any time you call process.nextTick() in a given phase, all callbacks passed to process.nextTick() will be resolved before the event loop continues.
In other words, Node starts processing the nextTickQueue once the current operation is completed, and it will continue until the queue is empty before continuing with the event loop.
This means that if process.nextTick() is called while the nextTickQueue is processing, the callback is added to the queue and it will be processed before the event loop continues.
The doc warns:
This can create some bad situations because it allows you to "starve" your I/O by making recursive process.nextTick() calls, which prevents the event loop from reaching the poll phase.
...and as it turns out you can starve your Promise callbacks as well:
test('Promise and process.nextTick order', done => {
const order = [];
Promise.resolve().then(() => { order.push('2') });
process.nextTick(() => {
Promise.resolve().then(() => { order.push('7') });
order.push('3'); // this runs while processing the nextTickQueue...
process.nextTick(() => {
order.push('4'); // ...so all of these...
process.nextTick(() => {
order.push('5'); // ...get processed...
process.nextTick(() => {
order.push('6'); // ...before the event loop continues...
});
});
});
});
order.push('1');
setTimeout(() => {
expect(order).toEqual(['1','2','3','4','5','6','7']); // ...and 7 gets added last
done();
}, 0);
});
So in this case the nested process.nextTick() callback that logs element.state() ends up running before the Promise callbacks that would set state.error to 'my error'.
It is because of this that the doc recommends the following:
We recommend developers use setImmediate() in all cases because it's easier to reason about
If you change your process.nextTick calls to setImmediate (and create your fetch mocks as functions so Promise.reject() doesn't run immediately and cause an error) then your test should work as expected:
it('throws error message when dealer submit fails', done => {
const mockComponentDidMount = () => Promise.resolve(
new Response(JSON.stringify({"data":[{"key":22,"value":"Stationstraat 5"}],"default":22}), {
status: 200,
headers: { 'content-type': 'application/json' }
})
);
const mockButtonClickFetchError = () => Promise.reject(new Error('my error'));
jest.spyOn(global, 'fetch').mockImplementation(mockComponentDidMount);
const element = mount(<DealerSelection />);
setImmediate(() => {
jest.spyOn(global, 'fetch').mockImplementation(mockButtonClickFetchError);
const button = element.find('button');
button.simulate('click');
setImmediate(() => {
console.log(element.state()); // state.error is 'my error'
global.fetch.mockClear();
done();
});
});
});
There are several asynchronous calls required to update the state, so your process.nextTick() isn't sufficient. To update the state, this needs to happen:
your test code clicks, and the event handler callback is queued
the event handler callback runs, runs fetch, gets a promise rejection, and runs the error handler
the error handler runs setState, which queues the state update (setState is asynchronous!)
your test code runs, checking the element's state
the state update runs
In short, you need to wait longer before asserting on the state.
A useful idiom to "wait" without nested process.nextTick() calls is to define a test helper
function wait() {
return new Promise((resolve) => setTimeout(resolve));
}
and then do
await wait();
as many times as required in your test code. Note that this requires you to define test functions as
test(async () => {
})
rather than
test(done => {
})