React の state ってイミュータブルが前提になってます
深い部分のプロパティを変えるのが面倒です
state.foo.bar.baz[1] をインクリメントするだけでこうです

setState({
...state,
foo: {
...state.foo,
bar: {
...state.foo.bar,
baz: state.foo.bar.baz.map((x, i) => i === 1 ? x + 1 : x)
}
}
})

オブジェクトのプロパティを書き換えたらその情報をもとにイミュータブルなまま新しいオブジェクトを作ってくれるライブラリもありますが これだけのためにライブラリ入れるのもなという気持ちがあります
単純にこういうフックを使えば良い気がしました

const useMutableState = (init) => {
const [state, setState] = useState({ value: init })
const updateState = useCallback((update) => {
update(state.value)
setState({ value: state.value })
})
return [state.value, updateState]
}

useState の setState 関数は引数として受け取る値が同じならレンダリングをスキップします
オブジェクトのプロパティ変更だと同じオブジェクト扱いでレンダリングされません
それを回避するために setState に渡すオブジェクトの参照だけを毎回変えています

setState の不要なレンダリングを避けてくれるメリットを捨てていますが 普段の setState でも毎回新しいオブジェクトを渡していて毎回レンダリングされます

setState({ ...state, [prop]: value })

それならこれでもいいと思うんです
このフックの使い方はこういう感じで プロパティを直接変更します

const Component = () => {
const [state, updateState] = useMutableState(
{foo: {bar: {baz: [0, 0]}}}
)

const onClick = () => {
updateState(s => {
s.foo.bar.baz[1]++
})
}

return (
<button onClick={onClick}>{state.foo.bar.baz[1]}</button>
)
}

少し使っただけでは問題なさそうだったのですが 実際の使用になると問題がありました
state.foo をコンポーネントの props やフックの依存配列に渡すときです
foo の中に変更があったのに foo が同じオブジェクトなので変更を検知できません

複雑なオブジェクトを state に持つ場合って部分的なプロパティを props や依存配列に渡すことが多いので 実用はできなさそうです
やっぱり面倒なコードを書くかライブラリだよりになりそうです