Why isn't this.setState working here? - javascript

snapshot.forEach(function(childSnapshot) {
groupRef.child(childSnapshot.key).once("value", (snap) => {
this.setState.bind({
expenses: this.state.expenses.concat(snap.val()),
});
})
})
The error which I get is
undefined is not an object (evaluating '_this2.setState')
The state has been declared in the following way
constructor(props) {
super (props);
this.state = {
expenses: [],
};
}

It's because you're not calling or using bind correctly and you are referencing this which is different in the context you created by calling forEach with a traditional function callback. The first argument to bind is the context not arguments.
In this case just avoid it and use arrow functions so this is still your component reference.
snapshot.forEach((childSnapshot) => {
groupRef.child(childSnapshot.key).once("value", snap =>
this.setState(state => ({ expenses: [ ...state.expenses, snap.val()] }))
);
});
Note that you should use the callback style of setState as above when referencing existing state, since setState is asynchronous by the time your state is set it might have changed especially when setting in a loop in an async callback.

Related

How to turn a higher order "array function" into a higher order "regular function"?

As I am learning react at the moment I came across this concept of event handler using "setState".
It is a little complicated for my understanding as 3 functions within each other. Case in point.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this)
increment(){
this.setState(state => ({count: state.count+ 1}))
}
//....some other code
This code works. I tried to understand the increment function a little further but I can't understand why the following code will not work:
increment(){
this.setState(function anon(state){
count: state.count+1
})
}
What is wrong with this code?
function anon(state) {
count: state.count + 1
}
^ This function is neither creating an object, nor returning anything from the function. It is creating a label called count inside the function. So, it is not throwing a syntax error.
this.setState(state => ({ count: state.count+ 1 }))
^ This callback creates an object with count key and implicitly returns it. It is equivalent to the following code when you use a regular function
this.setState(function (state) => {
return { count: state.count+ 1 }
})
The only missing part is return.
increment(){
this.setState(function anon(state){
return {count: state.count+1}
})
}

if setState cant be used than what does

I'm trying to set a state of the user by getting a value from my database and then using it. For some reason the state does not update itself I have tried await and async. What other options exists if this one can't be reliable to make this be a value.
I do get the following error : Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
- node_modules/fbjs/lib/warning.js:33:20 in printWarning
- node_modules/fbjs/lib/warning.js:57:25 in warning
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:12196:6 in warnAboutUpdateOnUnmounted
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:13273:41 in scheduleWorkImpl
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:6224:19 in enqueueSetState
- node_modules/react/cjs/react.development.js:242:31 in setState
* null:null in componentWillMount$
- node_modules/regenerator-runtime/runtime.js:62:44 in tryCatch
- node_modules/regenerator-runtime/runtime.js:296:30 in invoke
- ... 13 more stack frames from framework internals
constructor() {
super();
this.state = {
userPhoneNumber: "",
};
}
async componentWillMount() {
await firebase.database().ref('/Users/' + firebase.auth().currentUser.uid).on('value', async snap => {
if (snap) {
await this._setPhone(snap.val())
} else {
this.props.navigation.navigate('Phone')
}
});
console.log(this.state.userPhoneNumber);
}
_setPhone = (snap) => {
const val = parseInt(snap, 10);
this.setState({
userPhoneNumber: val
})
};
If you are sure that you are receiving the correct value for snap. Then the issue that you have is that setState is asynchronous. That means it takes time for state to set.
Unfortunately they way you are checking your state to see if the value has been set is wrong.
You should use a callback in the setState function, so your setState would become:
this.setState({userPhoneNumber: val}. () => console.log(this.state.userPhoneNumber));
I would recommend taking a read of the following articles by Michael Chan that go into more detail about setting state
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
There are also a few issues with your use of async/await and promises it looks like you are mixing the syntax between them. You either use one or the other, not both. This article goes into detail about the differences between them.
this.setState does not return a promise so using await this.setState does nothing.
This is how I would refactor your code:
componentDidMount() { // componentWillMount is deprecated
// you are using a promise to access firebase so you shouldn't be using `async/await`
firebase.database().ref('/Users/' + firebase.auth().currentUser.uid).on('value', snap => {
if (snap) {
this._setPhone(snap.val()) // remove the await as it is not doing anything
} else {
this.props.navigation.navigate('Phone')
}
});
}
_setPhone = (snap) => {
const val = parseInt(snap, 10);
this.setState({ userPhoneNumber: val}, () => console.log(this.state.userPhoneNumber)) // include the callback to check the value of state
};
Updated question
You must be calling setState when the component has been unmounted. You need to check to make sure that your component is mounted before calling setState.
One way of doing it is by having a boolean flag that monitors when the component is mounted.
componentDidMount () {
this._isMounted = true;
}
componentWillMount () {
this._isMounted = false;
}
when you set your state you can do something like this
if (this._isMounted) {
this.setState({key: value});
}
You can see more about it here https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
Just set a _isMounted property to true in componentDidMount and set
it to false in componentWillUnmount, and use this variable to check
your component’s status
This it not an ideal solution but it is the simplest, as you really should be cancelling your promises etc, so that setState is never called as the promise has been cancelled.

how save to state and save to local variable result from fetch api

I have a function that returns json:
const mapDispatchToProps = dispatch => {
return {
articleDetail: (id) => {
return dispatch(articles.articleDetail(id));
}
}
};
I get the result of the call here:
class ArticleDetail extends Component {
constructor(props) {
super(props);
this.state = {
articleId: props.match.params.id,
asd: "",
art:{}
};
}
componentWillMount() {
this.props.articleDetail(this.state.articleId).then((res) => {
console.log(res.article);
this.setState({art:res.article})
});
this.setState({asd: "asda"})
}
console.log(res.article) return me: {id: 1, author: {…}, headline: "First test article", description: "sadasdsads", img_name: "D.png", …}
but I can't write this result in state, just outside the function, as I did with asd.
I would appreciate it if you would help me, maybe there is some way to write the result of this.props.articleDetail () in state.
I also wanted to ask if I could write the result of calling this function into a variable, and the function returns promise
And also, is it possible to set some variable over this function and record what my console.log "returns" to my external variable.
Thank you so much for your time.
how did you check if the state changed?
In order to properly check if the state has been updated apply a callback to the setState function like this (remember that setState is async):
this.setState({ art: res.article }, () => {
// this happens after the state has been updated
console.log(this.state.art);
});
in regards to your comment about setting the state in the lifecycle methid then it's perfectly fine as long as you do it in componentWillMount and not in componentDidMount.

ReactJS - how do I update nested and "normal" state properties?

This is how my state looks like:
constructor(props, context) {
super(props, context);
this.state = {
show: false,
btnLabel: 'GO!',
car: {
owner: false,
manufacturer: false,
color: false
}
};
}
and this is how I modify state:
handleClickFetchPrice() {
this.setState({btnLabel: 'Fetching data...' });
console.log(this.state.fetchPriceBtn);
const url = 'some url';
axios.get(url)
.then(res => {
let car = [...this.state.car];
car.owner = res.data.owner;
car.manufacturer = res.data.manufacturer;
car.color = res.data.color;
this.setState({car});
})
}
The attribute car is updated, but fetchPriceBtn is not - the output of console.log(this.state.fetchPriceBtn); is still GO!.
What am I overlooking? Why the fetchPriceBtn is not updated?
React setState is an asynchronous process - you don't know exactly when it will be updated, you can only schedule the update.
To achieve your desired functionality, you can provide a callback into the setState method.
this.setState({ btnLabel: 'Fetching data...' }, () => console.log(this.state.fetchPriceBtn))
You can learn more following the documentation on the method.
#christopher is right, setState is an asynchronous process. But when second time call handleClickFetchPrice() function your btnLabel is value will be equal to Fetching data...
As answered in previous answers setState is asynchronous, so your console.log can't catch up the state change immediately. Again as suggested you can use callback function to track this change but if you use console.log just for debugging or want to see what changes in your state you can do this in your render function. And using a callback just for debug is not a nice way. Its purpose somehow different and if you check the official documentation, componentDidMount method is being suggested for such logic.
render() {
console.log( this.state.foo );
return (...)
}
If you do that you see two console.log output, one before state change and one after.
Also, your state operations might be enhanced. You car property is not an array, but you are converting it to an array and setting it? Is this what you intend:
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color } } ) );
})
Here we are not mutating our state directly, instead we are using spread operator and setting the desired properties. For your example we are setting the whole property actually.
One last note, I think you want to do that something like that:
this.setState( { btnLabel: "fetching } );
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color }, btnLabel: "go" } ) );
})
If your intention is somehow to do a status change/check this might no be a good logic as you have seen setState is not synchronous. Do this carefully.

How to update multiple states using Immutability Helper | React JS

I am new to Immutability Helper React library, and I am trying to update multiple state values at the same time, but only the last calling method state is getting updated.
this is what I tried:
state : {l0: null, l1: null}
updateL0 = (l0) => {
if(l0){
this.setState(
update(this.state, {
l0: { $set: l0 }
})
);
}
};
updateL1 = (l1) => {
if(l1){
this.setState(
update(this.state, {
l1: { $set: l1 }
})
);
}
};
Current Output: l1: null, l2: Expected Value
Expected Output: l1: Expected Value , l2: Expected Value
You can't setState multiple times in one event handler this.state may be updated asynchronousl after setState.
From the documentation
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState will behave like this:
//sate is {name:"Ben",age:22}
this.setState({...this.state,age:23});
console.log(this.state.age);//will log 22
So if you setState multiple times in one event handler you are likely to not get the result you were hoping:
//sate is {name:"Ben",age:22}
this.setState({...this.state,age:23});
console.log(this.state.age);//will log 22
this.setState({...this.state,name:"Harry"});//age will still be 22
The better solution to this is not to use the callback but to write your functions as pure functions (no side effects like setState), pass state to your functions and have them return a new state:
updateL0 = (state,l0) => {
if(l0){
return update(
state,
{
l0: { $set: l0 }
}
);
}
return state;
};
updateL1 = (state,l1) => {
if(l1){
return update(
state,
{
l1: { $set: l1 }
}
);
}
return state;
};
//when you call it you can do:
const newState = updateL0(this.state,L0);
this.setState(updateL1(newState,L1));//note that I'm passing newState here
//or you can just nest updateL1 and updateL0
this.setState(updateL1(updateL0(this.state,L0),L1));
use spread operator (...) in setState method to update multiple state fields as following way
my state is
this.state ={
fields: {
name:'',
email: '',
message: ''
},
errors: {},
disabled : false
}
i'm updating
this.setState(
{...this.state,
fields:{name:'', email: '', message: ''},
disabled: false
});

Categories