ユーザ入力を監視して 1 入力ごとに処理するのではなく 一旦入力が完了したタイミングで処理したい
空行や行頭行末の空白を消すなど ユーザの入力のフォーマット関係は入力途中でやると問題が出る

そういうときは change イベントで処理すればいいんだけど React だと onChange が input イベントのリッスンになってる
軽く調べた感じだと本来の change イベントをリッスンする機能は用意されてなさそう
ツールの方針的に常に state と DOM 状態を同期させるべきだから仕方ないのかも
Preact だとそういう余計な変更はしてないから onChange が使えるんだけど……

直接 input の参照を取得すればできるから ref と useEffect 使って addEventListener で直接リスナ設定かな
と思ってたけど blur イベントというのがあった
フォーカスが外れたときだから change イベントとタイミングは一緒
ただ 変更されてなくてもイベントが起きるけど
変更なしなら setState で何も起きないから 気にしないことにして onBlur で state に反映する

ユーザ入力完了後に小文字化する例

const App = () => {
const [value, setValue] = useState("")
const onChange = event => setValue(event.target.value.toLowerCase())

return (
<div>
<input defaultValue={value} onBlur={onChange} />
<div>input value: {value}</div>
</div>
)
}

defaultValue じゃなくて value で良さそうだけど value だと input に入力できなくなる
再レンダリングされてないから大丈夫そうなのに React は value がついてて onChange がついてない場合は入力を毎回リセットするみたい
バグ対策なのかもしれないけど迷惑

defaultValue にすると value による state の反映ができないから blur イベントが起きて state が更新されたあとは div のところは小文字化されてるけど input はユーザ入力そのままで大文字が入ってる
key をつけて state 更新時には input を再作成して defaultValue を反映させる

const App = () => {
const [value, setValue] = useState("")
const onChange = event => setValue(event.target.value.toLowerCase())

return (
<div>
<input key={value} defaultValue={value} onBlur={onChange} />
<div>input value: {value}</div>
</div>
)
}

key までついて複雑になってきたし value を onChange で state に保持して blur イベントで state を変換する方が単純かもしれない

const App = () => {
const [value, setValue] = useState("")
const onChange = event => setValue(value.toLowerCase())
const onInput = event => setValue(event.target.value)

return (
<div>
<input value={value} onChange={onInput} onBlur={onChange} />
<div>input value: {value}</div>
</div>
)
}

React だと input は必ず常に state と同期させるという前提で考えたほうがいいのかも