anonym anonym - 7 days ago 4
React JSX Question

Combining year, month and day to create date in ReactJS

I have a form which has select dropdowns for year, month and day.

<select onChange={(event) => this.mergeDate('day', event.target.value)}>
<option value="1">1</option>
...
</select>

<select onChange={(event) => this.mergeDate('month', event.target.value)}>
<option value="1">January</option>
...
</select>

<select onChange={(event) => this.mergeDate('year', event.target.value)}>
<option value="1977">1e977</option>
...
</select>


I need to create date out of these values and store them in the
birthday
field of state. In order to do that, I created three additional properties as
date_day
,
date_mth
and
date_year
.

this.state = {
name: '',
email: '',
birthday: '',
country: '',
date_day: '',
date_mth: '',
date_year: ''
}


And on the select dropdown change, I created a function that merges the values using
Date()
function as follows:

mergeDate(type, value)
{

if (type === 'year') { this.setState({ date_year: value }) }
if (type === 'month') { this.setState({ date_mth: value }) }
if (type === 'day') { this.setState({ date_day: value }) }

console.log('merging date now '); // --> Works!

let newDate = new Date(this.state.date_year, this.state.date_mth, this.state.date_day);

console.log(newDate); // --> successful, but shows date value of last onChange event

this.setState({ birthday: newDate });

console.log(this.state); // --> {...., birthday: '', ....}

}


In this function, the second console.log shows the date set on last onChange event. The last console.log logs empty value in the
birthday
. I'm guessing the problem is because of
asynchronous behavior
of
setState
which I do not yet understand. Can anyone please explain to me the reason behind and solution to the problem?

==========

UPDATE



According to Asad,
setState
is
asynchronous
and any action to be done after setting the state is complete should be passed as a parameter to the setState function. I tried:

postUpdate()
{
let newDate = new Date(this.state.date_year, this.state.date_mth, this.state.date_day);
this.setState({ birthday : newDate });
}

mergeDate(type, value)
{
if (type === 'year') { this.setState({ date_year:value }, this.postUpdate(newDate))}
if (type === 'month') { this.setState({ date_mth: value }, this.postUpdate(newDate))}
if (type === 'day') { this.setState({ date_day: value }, this.postUpdate(newDate))}
}


This is still giving me the same output - shows date value of last onChange event. What am I missing?

========

SOLUTION



The solution is to not pass the function alone but as a function.

mergeDate(type, value)
{
if (type === 'year') { this.setState({ date_year:value }, () => this.postUpdate())}
if (type === 'month') { this.setState({ date_mth: value }, () => this.postUpdate())}
if (type === 'day') { this.setState({ date_day: value }, () => this.postUpdate())}
}

Answer

setState is asynchronous, as React often batches state updates for performance. This means you can't rely on fresh state being available in statements following setState. If you rely on ordering of changes, you should pass a callback, like so:

mergeDate(type, value)
{
        function postUpdate() {
            // All of the code from after your if statements
        }
        if (type === 'year')  { this.setState({ date_year: value }, postUpdate) }
        if (type === 'month') { this.setState({ date_mth: value  }, postUpdate) }
        if (type === 'day')   { this.setState({ date_day: value  }, postUpdate) }

}

In general though, you should simply be doing all your computations up front and just calling setState once at the end of the function with all your desired changes to the state.