useState is not this.setState, and the update is not merged

this.setState merges the object you pass into the existing state. The setter from useState replaces the value entirely, which is a silent behaviour change for anyone translating a class component by hand.

// class: merges. other keys survive.
this.setState({ loading: false });

// hook: replaces. 'error' and 'data' are gone.
const [state, setState] = useState({ loading: true, error: null, data: null });
setState({ loading: false });

// so either spread, or split the state up
setState(prev => ({ ...prev, loading: false }));
const [loading, setLoading] = useState(true);   // usually better

Splitting related-but-independent values into separate useState calls is almost always the better answer, and it is what the API is shaped for — one object of five fields is a reducer wearing a disguise. The functional form is required rather than optional whenever the new value depends on the old one, because the setter batches and a stale closure will otherwise compute from a value that is already gone.