🦧

useSWRMutationのisMutatingのロジックを見てみた

に公開

フォームでのpost処理を実装するにあたり、今回SWRのuseSWRMutationを使ってみたのですが、ダブルクリックを防止するのにisMutatingではうまくいきませんでした。
そこでisMutatingがどうやって実装されているのかロジックを見てみました。

前提

React: v18.2.0
swr: v2.3.3
React19ではuseTransition()という機能があるが、今回は18なので使えない。
https://ja.react.dev/reference/react/useTransition#displaying-a-pending-visual-state-during-the-transition

コードを見ていく

では、実際に見ていきます。
https://github.com/vercel/swr/blob/main/src/mutation/index.ts

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の中身を見てみます。
https://github.com/vercel/swr/blob/main/src/mutation/state.ts

細かいところは割愛しますが、こんな感じの処理の流れになっています。

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を使い再レンダリングされている?ようでした。
つまり、ダブルクリックなどで複数実行されるとこんな感じになっている?

  1. trigger実行
  2. isMutatingを更新、再レンダリング(非同期)
  3. 再度trigger実行(再レンダリング完了前)
  4. 再レンダリングの途中のため更新前のisMutating(false)を取得
  5. 2回目のAPIリクエストが開始される

結論

isMutatingではダブルクリックなどの複数実行には対応できないようで、独自でuseRefなどを使って制御する必要がありそうでした。

ランサーズ株式会社

Discussion