You're close. You have to spread the nested objects properties back into it just like you did with prevState.
setProperties(prevState => ({...prevState, values: {...prevState.values, b: e.target.value}}))
Spreading prevState makes a new object out of its values. But when you specify a key to overwrite the values taken from prevState are gone. Basically, you aren't merging prevState.values with the new values. You're overwritting it.
To fix this you need to provide the whole nested object again. The easiest pattern for that is the one above. Spread prevState.values, then overwrite the nested key you want to change.
Note: If you use an event in a functional update, you need to either save the value to another variable, or use e.persist() first. The reason is that React reuses event objects for performance reasons, so e will be reset before the async update happens. using persist removes the event from the pool for reuse, and you may access its values normally.
Example:
const handleChange = (e) => {
e.persist();
setProperties(prevState => (
{
...prevState,
values: {...prevState.values, b: e.target.value}
}
))
// OR
let value = e.target.value;
setProperties(prevState => (
{
...prevState,
values: {...prevState.values, b: value}
}
))
}
As another note, I only used a functional update in my answer because that is how the question was given. But in this usecase it is not required. You may use a normal state update safely for this handler.