'React-Bootstrap Form.Check weird behaviour on first click

I'm trying to do filtering on a list of objects, and have made a useState object which will hold the state of all filters, which looks like this:

const [filterState, setFilterState] = useState({
    "sell": false,
    "buy": false,
    "startDate": "",
    "endDate": "",
    "location": "",
    "music": false,
    "theatre": false,
    "movie": false,
});

I'm using two Form.Check from "react-bootstrap" to change the "sell" and "buy" fields, but they are giving me some issues, the filterState object doesn't change until I have clicked the Form.Check objects twice, only the UI updates correctly, which is weird when the filterState object decides the checked state. So what is shown is the opposite of what the filterState object's state is.

Code for the Form.Check fields:

<Form.Check checked={filterState.buy} onChange={onBuyChange} id="buy" type="checkbox" label={`Ønskes kjøpt`}/>
<Form.Check checked={filterState.sell} onChange={onSellChange} id="sell" type="checkbox" label={`Til salgs`}/>

The onChange functions:

const onSellChange = () => {
    setFilterState({
            ...filterState,
            "sell": !filterState.sell
        }
    );
    console.log(filterState);
}

const onBuyChange = () => {
    setFilterState({
            ...filterState,
            "buy": !filterState.buy
        }
    );
    console.log(filterState);
}

Examples

Initial state:
enter image description here

Clicking once doesn't change the state:
enter image description here

Clicking again updates the state, but it's now it's of sync:
enter image description here


Printing before and after setFilterState

Another weird behavior I get is that printing the filterState before and after setFilterState gives the same output. Should filterState be changed before it goes to the next line? Is setFilterState working asynchronously somehow, which can make Form.Check read from it before the value has changed? enter image description here



Solution 1:[1]

Solution

useState's set function runs asynchronously, so I needed useEffect.
Using useEffect:

useEffect(() => {
    filterEvents();
}, [filterState]);

With the same code as above, this filters the events displayed whenever the filterState changes, which is what I needed. Thanks to Juho Vepsäläinen for the help.

Solution 2:[2]

Can you try this?

const onSellChange = () => {
    setFilterState(filterState => ({
            ...filterState,
            "sell": !filterState.sell
        })
    );
}

const onBuyChange = () => {
    setFilterState(filterState => ({
            ...filterState,
            "buy": !filterState.buy
        })
    );
}

I cannot guarantee it will work but it feels like the problem has to do with the way state is derived so I made your setters pick up the previous one called async. That way React should be able to resolve it correctly.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Marcus Hagberg
Solution 2