🦧
useSWRMutationのisMutatingのロジックを見てみた
フォームでのpost処理を実装するにあたり、今回SWRのuseSWRMutationを使ってみたのですが、ダブルクリックを防止するのにisMutatingではうまくいきませんでした。
そこでisMutatingがどうやって実装されているのかロジックを見てみました。
前提
React: v18.2.0
swr: v2.3.3
React19ではuseTransition()という機能があるが、今回は18なので使えない。
コードを見ていく
では、実際に見ていきます。
useStateWithDepsという関数が使われていて、更新時はsetStateを使っているようです。
const [stateRef, stateDependencies, setState] = useStateWithDeps<{
data: Data | undefined
error: Error | undefined
isMutating: boolean
}>({
data: UNDEFINED,
error: UNDEFINED,
isMutating: false
})
...
setState({ isMutating: true })
次に、useStateWithDepsの中身を見てみます。
細かいところは割愛しますが、こんな感じの処理の流れになっています。
const setState = useCallback((payload: Partial<S>) => {
// 再レンダリングが必要かどうかのフラグ
let shouldRerender = false
// 現在の状態への参照を取得(useRefで管理されている)
const currentState = stateRef.current
// ペイロードの各プロパティをループ処理
for (const key in payload) {
// 安全なプロパティチェック(継承されたプロパティを除外)
if (Object.prototype.hasOwnProperty.call(payload, key)) {
// TypeScriptの型安全性のため、keyを型キャスト
const k = key as keyof S
// 値が変更された場合のみ処理
if (currentState[k] !== payload[k]) {
// 同期的に状態を更新(useRefなので即座に反映される)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
currentState[k] = payload[k]!
// プロパティがコンポーネントで使用されている場合、再レンダリングが必要
if (stateDependenciesRef.current[k]) {
shouldRerender = true
}
}
}
}
// 再レンダリングが必要かつコンポーネントがアンマウントされていない場合、
// useStateのsetterを呼び出して再レンダリング
if (shouldRerender && !unmountedRef.current) {
rerender({})
}
}, [])
コンポーネントで使用されている場合に、値が更新されるとuseStateのsetterを使い再レンダリングされている?ようでした。
つまり、ダブルクリックなどで複数実行されるとこんな感じになっている?
- trigger実行
- isMutatingを更新、再レンダリング(非同期)
- 再度trigger実行(再レンダリング完了前)
- 再レンダリングの途中のため更新前のisMutating(false)を取得
- 2回目のAPIリクエストが開始される
結論
isMutatingではダブルクリックなどの複数実行には対応できないようで、独自でuseRefなどを使って制御する必要がありそうでした。
Discussion