Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Component's state not updating with select and onChange handler

I made a component that load data via xhr on the user select a value of <select> element.

class SomeComponent extends Component {
    state = {
        data: [],
        currentCategory: 'all'
    }
    switchCategory = (ev) => {
        console.log('Selected category is ' + ev.target.value);

        this.setState({
            currentCategory: ev.target.value
        });

        this.loadData();
    }
    loadData = async () => {
        let { currentCategory } = this.state;

        // Always print previous value!!!
        console.log(currentCategory);

        // Get data via XHR...
    }
    render() {
        return (
            <div>
                <select value={currentCategory} onChange={this.switchCategory}>
                    <option value="all">All</option>
                    {categories.map( category => 
                    <option key={category._id} value={category.category}>{category.display}</option>    
                    )}
                </select>

                <table>
                    // ... prints data with this.state.data
                </table>
            </div>
        );
    }
}

Above code is just in brief. Code is quite simple, I just synchronize a value of the select element with this.state.currentCategory, and detect it's switching with switchCategory method of the class.

But the main problem is that when I access the component's state, it always contains previous value, not a present. You can see that I updating the currentCategory on value of the select changes.

    switchCategory = (ev) => {
        console.log('Selected category is ' + ev.target.value);

        this.setState({
            currentCategory: ev.target.value
        });

        this.loadData();
    }

So in this situation, this.state.currentCategory must not has "all", like something else "Apple", but still it contains "all", not an "Apple"!

    loadData = async () => {
        let { currentCategory } = this.state;

        // Always print previous value!!! I expected "Apple", but it has "all"
        console.log(currentCategory);

        // Get data via XHR...
    }

So eventually XHR occurs with previous value, and it gives me wrong data that I didn't expected. After that, choosing other value of the select(let's call it Banana), it has an Apple, not a Banana!

As I know setState is "sync" job, so calling this.switchCategory will happens after updating states, so it must have present value, not a previous.

But when I print the component's state in console, it isn't.

So, what am I missing? Why am I always getting old data, not present? If I doing something wrong approach, then what alternatives can I do?

Any advice will very appreciate it. Thanks!

like image 944
modernator Avatar asked Sep 11 '25 04:09

modernator


1 Answers

The problem here is that setState is async (it can be sync in certain situations). That's why you are get previous value.

There are two possible solutions.

//
// 1. use value directly.
//
switchCategory = (ev) => {
    this.setState({ currentCategory: ev.target.value });
    this.loadData(ev.target.value);
}

loadData = async (currentCategory) => {
    console.log(currentCategory);

    // Get data via XHR...
}

//
// 2. use completition callback on `setState`.
//
switchCategory = (ev) => {
    this.setState({ currentCategory: ev.target.value }, () => {
        this.loadData(ev.target.value);
    });
}

loadData = async () => {
    const { currentCategory } = this.state;
    console.log(currentCategory);

    // Get data via XHR...
}

Article on synchronous setState in React [link]

like image 200
Andreyco Avatar answered Sep 12 '25 19:09

Andreyco