I have react js and asp.net core application, onform submit i am trying to fill some master data and detail data(with in aray).
The problem is that, setState updates one previous value and my backend asp.net web api method gets hit when i press submit button twice. anyone can please tell me what is the proper way to update state so that i could submit correct data on first hit. I am calling another function addDetailItem from handleSubmit function to fill array using setState hook.
<form autoComplete="off" noValidate onSubmit = {handleSubmit} >
const handleSubmit = (e) => {
e.preventDefault();
if(validateForm()){
addDetailItem();
props.createPaymentVoucher(values);
}
console.log(values);
}
const addDetailItem = () => {
let x = {
voucherMasterId:values.voucherMasterId,
voucherDetailId:0,
accountCode: values.creditAccountCode,
debit: 0,
credit: values.creditAmount,
remarks: values.fromRemarks,
status: ""
}
setValues({
...values,
voucherDetails: [...values.voucherDetails, x]
});
console.log('voucherDetails: ', values.voucherDetails);
}
here setValues setting the values but on first click it shows empty array and on second click it shows values which had to filled first time.
Solution
Just capture values changes using useEffect
I passed the values as dependency in useEffect.
So, You will get the latest updated value of values
Now everytime anything changes in values, it will call props.createPaymentVoucher
useEffect(() => {
props.createPaymentVoucher(values);
}, [values])
const handleSubmit = (e) => {
e.preventDefault();
if(validateForm()){
addDetailItem();
}
}
const addDetailItem = () => {
let x = {
voucherMasterId:values.voucherMasterId,
voucherDetailId:0,
accountCode: values.creditAccountCode,
debit: 0,
credit: values.creditAmount,
remarks: values.fromRemarks,
status: ""
}
setValues({
...values,
voucherDetails: [...values.voucherDetails, x]
});
}
setState is async function so when you try to use state immediately after updating it, it will always return previous value.
So instead of using state value immediately, you can use useEffect or either follow below solution for your specific use case.
Try:-
const newValues = { ...values, voucherDetails: [...values.voucherDetails, x]};
setValues({ ...newValues });
return newValues;
Now you can access your values in your handleSubmit function like below
const updatedValues = addDetailItem();
Related
I am creating a crypto react application and just trying to figure something out in which a function is returning an empty array when I update one of the state values. I am thinking it is a race condition. It works until there is a state change. Please see my code below:
So I have a state object. And it is returning an array of currencies by default based on the selectedCurrencyState.selectedFromCurrency value, which is a state value. This function basically needs to determine the quoteAsset before returning the object that contains the array of baseCurrencies. swapInfo is the state value containing the mock data. The mock data is nested like so:
const getBaseCurrencies = () => {
const filteredQuoteAssetObj = selectedCurrencyState.selectedFromCurrency
&& swapInfo.find(item =>
item.quoteAsset === selectedCurrencyState.selectedFromCurrency);
const currencies = filteredQuoteAssetObj
&& filteredQuoteAssetObj.baseAssets.map(item => item.baseAsset);
return currencies;
}
Here is the mock data that I am using:
Like I said, it works until the state change of selectedCurrencyState.selectedFromCurrency(switch state from USD to SHIB). Afterwards, it returns an empty array. By default I have the state set to USD as the selectedFromCurrency.
Any help or input is appreciated. Thanks guys!
--UPDATED--
Here is the way I am updating the state by the way. I am using a functional component therefore, using useState.
const [swapInfo, setSwapInfo] = useState([]);
const [fromCurrencies, setFromCurrencies] = useState([]);
const [selectedCurrencyState, setSelectedCurrencyState] = useState({ selectedToCurrency: null, selectedFromCurrency: null });
const [transactionType, setTransactionType] = useState(TRANSACTION_TYPES.BUY);
useEffect(() => {
getSwapPairs()
.then((res) => {
setSwapInfo(res.data);
setFromCurrencies(res.data.map(item => item.quoteAsset));
if (transactionType === 'BUY') {
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: localStorage.getItem('fromCurrency') || 'USD', selectedToCurrency: symbol || localStorage.getItem('toCurrency') });
}
setTransactionType(localStorage.getItem('transactionType', transactionType) || TRANSACTION_TYPES.BUY);
}, []);
const handleInvertSelectedAssets = (fromCurrency, toCurrency) => {
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: toCurrency, selectedToCurrency: fromCurrency });
localStorage.setItem('toCurrency', fromCurrency);
localStorage.setItem('fromCurrency', toCurrency);
};
Here is where I switch the transactionType:
const handleTransactionTypeChange = (type) => {
if (transactionType !== type) {
setTransactionType(type);
handleInvertSelectedAssets(selectedCurrencyState.selectedFromCurrency, selectedCurrencyState.selectedToCurrency);
localStorage.setItem('transactionType', type);
setAmountState({ toCurrencyAmount: '', fromCurrencyAmount: '' });
}
};
Feel like your problem lies in the way you update your state.
Maybe there is a typo on the updated selectedCurrencyState.selectedFromCurrency or maybe swapInfo is somehow updated in an invalid way ?
Could you fill in more info about how your state is managed ?
When you update state in React,
The following should not be done
this.state = {a:"red", b:"blue"};
if( this.state.b == "blue" ) {
this.setState({a: "green" });
}
Instead, do
this.setState((prevState)=>(
{a: (prevState.b=="blue")? "green": "red" }
));
In other words, state updates that depend on its previous value, should use the callback argument of the setState.
Eg: selectedCurrencyState depends on transactionType.
You can store both (or all state properties) as a single object.
With a checkbox onChange event, how do I remove value from state array when unchecked in react?
State array:
this.state = { value: [] }
onChange function:
handleChange = event => {
if (event.target.checked) {
this.setState({
value: [...this.state.value, event.target.value]
});
} else {
this.setState({
value: [this.state.value.filter(element => element !== event.target.value)]
});
}
};
Not sure exactly what the .filter() should be doing
You're very close, except:
You need to remove the [] around your call to filter. filter returns an array. If you wrap that in [], you're putting the array inside another array, which you don't want (in this case).
and
Since you're updating state based on existing state, it's important to use the callback version of setState, not the version that directly accepts an object. State updates can be batched together, so you need to be sure you're dealing with the most recent version of the array.
So:
handleChange = ({target: {checked, value: checkValue}}) => {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^− destructuring to take the properties from the event,
// since the event object will get reused and we're doing
// something asynchronous below
if (checked) {
this.setState(({value}) => ({value: [...value, checkValue]}));
} else {
this.setState(({value}) => ({value: value.filter(e => e !== checkValue)}));
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^−−− No [] around this
}
};
There are some situations where you'd get away with using this.state.value instead of using the callback (for instance, if you only update value in response to certain events), but you have to be sure you know which ones they are; it's simpler just to use the callback.
FWIW, since it has multiple values in it, if it were me I'd call the state property values (plural) rather than value, which would also mean we didn't have to rename the value from the event target in the destructuring above:
handleChange = ({target: {checked, value}}) => {
if (checked) {
this.setState(({values}) => ({values: [...values, value]}));
} else {
this.setState(({values}) => ({values: values.filter(e => e !== value)}));
}
};
Unfortunately I didn't understand logic of input checkbox in render. Here is the problem:
1) I have input type checkbox with onChange and checked attributes on it;
2) Also I have button handleSearch which gets API information from backend after clicking the button;
3) If I will click on checkbox it will also receive information from API(from same API as in second step with same parameters).
Problem is: If I will click checkbox it will send falsy parameter of checkbox because, as I understand, it is working vice-versa for some reason. But, if I will try to first click button it will work OK.
So I need to send truthy parameter on handling checbox.
input in render():
<input
type="checkbox"
className="custom-control-input"
name="grouping"
id="updateprice"
checked={grouping}
onChange={this.onGroupingChange}
/>
checkbox handler():
onGroupingChange = (e) => {
const {grouping} = this.state;
this.setState({ grouping: e.target.checked});
this.getSales(grouping);
};
OnClick handler():
handleSearch = () => {
const { grouping } = this.state;
this.setState({ isLoading: true });
this.getSales(grouping);
};
getSales()
getSales = (grouping) => {
let notattr;
if (grouping===false){
notattr=1
}
else notattr = 0
this.setState({isLoading: true})
Axios.get('/api/report/sales', {
params: { notattr }
})
.then(res => res.data)
.then((sales) => {
this.setState({
sales,
isLoading: false,
handleGrouping: true,
activePage: 1,
currentRange: {
first: (this.state.itemsPerPage) - this.state.itemsPerPage,
last: (this.state.itemsPerPage) - 1
},
orderBy: ''
})
})
.catch((err) => {
this.setState({isLoading: false})
console.log(err)
})
};
basic problem scenario 1:
1) I'm opening page;
- checkbox in screen true;
2) I'm clicking search button;
- API sends noattr:0 because grouping:true;
3) Now, I want to click checkbox;
- API still sends noattr:0 because grouping:true(but I was expecting grouping:false value)
4) If I will handle checkbox later it will work vice-versa. But if I will handle search button, it will send OK information.
Obviously there is small mistake somewhere, but I tried a lot of different combinations and it seems that didn't find right one.
onGroupingChange = (e) => {
const {grouping} = this.state;
this.setState({ grouping: e.target.checked});
this.getSales(grouping);
};
I think you should call like this:
this.getSales(e.target.checked)
You're calling getSales function with previous value, not updated value.
I'm trying to update a state using useState hook, however the state won't update.
const handleSelect = address => {
geocodeByAddress(address)
.then(address => {
const receivedAddress = address[0];
const newAddress = {
street: receivedAddress.address_components[1].long_name,
number: receivedAddress.address_components[0].long_name,
city: receivedAddress.address_components[3].long_name,
country: receivedAddress.address_components[5].long_name,
zip: receivedAddress.address_components[6].long_name,
selected: true
};
handleAddressSelection(newAddress);
})
.catch(error => console.log(error));
};
When handleSelect is called, it creates the object newAddress, and then calls handleAddressSelection passing newAddress.
function handleAddressSelection(newObj) {
console.log(newObj);
Object.keys(newObj).forEach(function(key) {
setValues({ ...values, [key]: newObj[key] });
});
}
In console.log(newObj) the object is filled fine, with all the data I need. Then I call setValues for each object in newObj, however no matter what, the values object won't receive the new data. The only one that is updated is selected: true, all others won't update.
What should I do to fix it?
You're calling setValues multiple times in a loop, and every time you do so, you spread the original values, and thus overwrite anything that was done on the previous setValues. Only the very last setValues ends up working, which happens to be the one for selected: true
If you need to base your update on the previous value of the state, you should use the function version of setValues, as in:
Object.keys(newObj).forEach(function(key) {
setValues(oldValues => ({ ...oldValues, [key]: newObj[key] }));
});
But even better would be to only call setValues once. If you're calling it multiple times, then you're going to generate multiple renders. I'd do this:
setValues(oldValues => ({...oldValues, ...newObj}));
Values is not even defined anywhere in your examples. My guess is, it's some cached copy and you should be using callback variant of the state setter instead:
setValues(previousValues => ({ ...previousValues, [key]: newObj[key] }));
In my react component, when the component mounts, I want to run the loadData() function to populate an array of people objects in the state.
This component is called UserDisplaySection.js
componentDidMount() {
this.loadData();
}
Which calls the function to load data:
loadData = () => {
const db = fire.database();
const ref = db.ref('User');
let currentState = this.state.people;
ref.on('value', (snapshot) => {
snapshot.forEach( (data) => {
const currentStudent = data.val();
let user ={
"email" : currentStudent.Email,
"name": currentStudent.Name,
"age": currentStudent.Age,
"location": currentStudent.Location,
"course": currentStudent.Course,
"interests": currentStudent.Interests
}
currentState.push(user);
});
});
this.setState({
people: currentState
});
}
From another component called Home.js, I render UserDisplaySection.js with the toggle of a button which sets the state of a boolean called willDisplayUser which is initialised as false by default.
handleDisplayUsers = e => {
e.preventDefault();
this.setState({
willDisplayUsers: !(this.state.willDisplayUsers),
});
}
render() {
return (
<div>
<button className="mainButtons" onClick={this.handleDisplayUsers}>View Users</button> <br />
{this.state.willDisplayUsers ? <UserDisplaySection/>: null}
</div>
);
}
Note: The function works and populates the state with the correct data, but only after I render the UserDisplaySection.js component for a second time and works perfectly until I reload the page.
By doing ref.on() you are actually declaring a listener that "listens for data changes at a particular location", as explained in the doc. The "callback will be triggered for the initial data and again whenever the data changes". In other words it is disconnected from your button onClick event.
If you want to fetch data on demand, i.e. when your button is clicked, you should use the once() method instead, which "listens for exactly one event of the specified event type, and then stops listening".
The root of the problem is that you are setting state outside of the ref.on callback.
Data fetch is async and the callback will be executed later in time. Since set state is outside the callback, it gets called called immediately after registering a listener, but before the data finished fetching. That's why it does not work on initial render/button click: it shows initial state, not waiting for the fetch to finish.
Also, using once instead of on might serve better for your case.
loadData = () => {
const db = fire.database();
const ref = db.ref('User');
let currentState = this.state.people;
ref.once('value', (snapshot) => {
// callback start
snapshot.forEach( (data) => {
const currentStudent = data.val();
let user ={
"email" : currentStudent.Email,
"name": currentStudent.Name,
"age": currentStudent.Age,
"location": currentStudent.Location,
"course": currentStudent.Course,
"interests": currentStudent.Interests
}
currentState.push(user);
});
this.setState({
people: currentState
});
// callback end
});
// setState used to be here, outside of the callback
}