Is one allowed to call a setState inside the function passed into setState. for instance im trying to check for errors in user input and im calling the error checker function inside the setInpuForm, I know I can call the checkerror() from outside the setInputForm i.e inside the change handler, but out of curiosity, i decided to try it this way. But I discovered after typing the first character into the input field no changes but from the second entry up wards,its displayed. When i ran this it worked, the issue is that the very first time the input changed, the value was not updated but subsequently it worked
const errorChecker = (inputValue, element)=>{
//check for password error
if (element==='password') {
if (inputValue.length<=6) {
console.log('function error checker called')
setInputForm((form)=>{
console.log('after error check')
console.log(form.password.value)
return {
...form,
[element]: {
...form[element],
error: 'Password can not be less than 6'
}
}
})
} else {
setInputForm((form) => {
return {
...form,
[element]: {
...form[element],
error: ''
}
}
})
}
}}
<Input
key={inputForm[it].label}
changed={(event) => {
const val = event.target.value
setInputForm((form)=>{
console.log(form.password.value)
console.log('before error check')
errorChecker(val, it)
console.log('from set valuie')
return {
...form,
[it]: {
...form[it],
value: val
}
}
})
}}
I believe your issue is that you are calling two setState functions one after another, both of which rely on the previousState value. setState as you may know is not synchronous, and needs some time to finish its execution. In the meantime what you would be using in your errorChecker function would be the last state registered.
In order to avoid this, in a class based component you would usually include a callback function on your this.setState function. In a functional component you cannot do that. Instead you can use useEffect in order to detect changes to a certain state, and trigger a function like errorChecker based on those changes.
For example:
useEffect(() => {
if (inputForm[it].value !== "") {
errorChecker(inputForm[it].value, inputForm[it]);
}
}, [inputForm[it].value);
Related
I have a local state selectedProducts holding a number representing the number of products the client has selected, while exiting the screen I want to update the number in redux so it can be viewed in the cart on a different screen.
but every time my local selectedProducts updates it stacks another function call on beforeRemove event
I know this is a noob problem but I've spent hours trying to find a solution to this problem and advice would be very helpful :)
useEffect(()=>{
navigation.addListener('beforeRemove', () => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
dispatch(addToCartMultipleQty(selectedProducts));
});
},[selectedProducts])
selectedProducts is a number state whose initial value is null and on a button click event its value is either incremented or decremented by 1, if its previous value is null then its value is set to 1
Note: I just want to update selectedProducts state's latest value only once in redux when the screen is about to be exited/unmonted
You can try this:
useEffect(()=>{
navigation.addListener('beforeRemove', () => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
dispatch(addToCartMultipleQty(selectedProducts));
});
return () => {
navigation.removeListener('beforeRemove');
}
}, [selectedProducts])
Add that in return at the end of useEffect it will work as componentWillUnmount in functional component
useEffect(() => {
return () => {
// Anything in here is fired on component unmount.
}
}, [])
Edit: In Your case
useEffect(() => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
return () => {
// Anything in here is fired on component unmount.
if (selectedProducts) {
dispatch(addToCartMultipleQty(selectedProducts));
}
};
}, [selectedProducts]);
I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}
I am developing a React Native application and am facing the following error:
I have defined a useRef which stores the doc ID from a firebase collection. But when I call that variable after it has been defined, the .current value returns a blank string.
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
bidId.current = doc.id
console.log(bidId.current)
}
})
})
The above code returns the expected value. However, when I call the variable outside this db.collection loop, I get the following value:
But calling the bidId.current returns a blank string.
Please can someone help me with this. Thanks!
Actually this is what happens:
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
bidId.current = doc.id
// This line gets executed after some time!
console.log(bidId.current)
}
})
})
// This gets executed first! (The value has not been stored yet!)
console.log(bidId.current);
Using the "useState" hook instead of "useRef" will solve the issue. Consider the following code:
const [BidId, setBidId] = useState<string | null>(null);
// This will store the value...
useEffect(() => {
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
setBidId(doc.id);
}
})
})
}, []);
// Here you can access the value
useEffect(() => {
if(BidId !== null)
console.log(BidId);
}, [BidId]);
// You can also return the component like the following:
return (<View>The Bid ID is: {BidId !== null ? BidId : "Loading..."}</View>);
Your useEffect basically says that whenever pageRef changes, call this function. If done outside, it will call do your tasks on every render instead of doing the whenever pageRef values is changed. Also, in initial renders, it may give undefined values.
You can only return a function in useEffect which basically says that before running the same next time, run this function before.
Try (currentUser without the '?' query character):
if (doc.data().email === auth.currentUser.email) {
bidId.current = doc.id
console.log(bidId.current)
}
I'm working with controlled form and have handleChange function, that get input value and save it in state like state.mainField.firstInput.
handleChange = (e) => {
// the input value was 2, then I enter 3
console.log(this.state.mainField.firstInput); // expect 2
console.log(this.state); // expect { mainField: { firstInput: 3 } }
/*
...
*/
this.setState({ mainField: newData });
}
/*
...
*/
<form>
<input value={this.state.mainField.firstInput} onChange={this.handleChange} />
</form>
When I'm trying print state.mainField.firstInput to the console on the top of the handleChange function I got different result with state in the same field. Exact firstInput property was current state value and property in object this.state was like after setState functions. Why this same values are different?
There are two things to note here
setState is asynchronous and hence it won't reflect the change immediately
Secondly when you log an object with console.log(), it is evaluated after you expand it and hence by that time the value is updated. Hence you see the difference between
console.log(this.state.mainField.firstInput); // expect 2
console.log(this.state); // expect { mainField: { firstInput: 3 } }
JavaScript is an synchronous and single-threaded language.
So it runs line-by-line
You are console-logging before your state changes so It'll obviously give 2.
Even if you console.log after setting the state then also you may not get the expected result because the set state takes time to execute.
// This may or may not work
handleChange = (e) => {
// the input value was 2, then I enter 3
console.log(this.state.mainField.firstInput); // expect 2
this.setState({ mainField: newData });
console.log(this.state); // expect { mainField: { firstInput: 3 } }
}
But this will surely work
handleChange = (e) => {
// the input value was 2, then I enter 3
console.log(this.state.mainField.firstInput); // expect 2
this.setState({ mainField: newData },()=>{
console.log(this.state); // expect { mainField: { firstInput: 3 } }
});
}
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.