【Vuex/Pinia】非同期処理のコミットでハマる!状態管理の落とし穴と解決策

VuexやPiniaで非同期処理を扱っている際、APIのレスポンスをストアに反映させようとして「TypeError: Cannot read properties of undefined」が発生したり、状態が意図通りに更新されなかったりすることはありませんか?

私も先日この挙動でハマったので、備忘録として解決策をまとめます。

この記事でわかること

  • 非同期処理後のコミットで発生するエラーの主な原因
  • ペイロード構造の不一致を防ぐための実装手法
  • Vuex/Piniaにおける堅牢なエラーハンドリングの書き方

[現象] 状態が更新されない、またはundefinedエラーが発生

非同期処理(APIリクエストなど)を実行し、その結果を commit または patch した直後に以下のようなエラーが発生する、あるいはエラーは出ないもののStateが nullundefined のまま更新されないという現象です。

TypeError: Cannot read properties of undefined (reading 'someProperty')

または

Store state not updating as expected after dispatching action.

[環境]

  • フレームワーク: Vue.js 2.x / 3.x, Nuxt.js
  • 状態管理ライブラリ: Vuex 3.x / 4.x または Pinia
  • 通信ライブラリ: Axios / Fetch API

[原因] ペイロード形式の不一致と不十分なバリデーション

主な原因は、アクション(Action)内で取得したデータ構造と、ミューテーション(Mutation)が期待するデータの型が一致していないことにあります。

特に以下のケースで頻発します。

原因の分類具体的な内容
構造の不一致APIレスポンスの response.data をそのまま渡しているが、
ミューテーション側は { user: ... } のようなラップされた構造を期待している。
非同期の考慮不足awaitを忘れている、またはPromiseが解決される前に状態を参照しようとしている。
未定義データの放置APIが200 OKを返しても、
中身のデータが空(null)であるケースをハンドリングしていない。

[解決策] 正確なペイロード構築とエラーハンドリング

解決のためには、ミューテーションにデータを渡す前に必ずデータ構造をチェックし、必要なプロパティのみを抽出して渡すように修正します。

修正コード (Before / After)

// Before (問題のあるAction)
async commitLoginResult({ commit }) {
  try {
    const response = await api.login(credentials);
    // レスポンスの構造を検証せずにそのままコミットしている
    commit('SET_USER_DATA', response.data.user);
  } catch (error) {
    commit('SET_ERROR', 'Login failed');
  }
}

// After (改善されたAction)
async commitLoginResult({ commit }) {
  try {
    const response = await api.login(credentials);
    
    // 1. データの存在チェックを徹底する
    if (response && response.data && response.data.user) {
      // 2. ミューテーションが期待する形式に整形して渡す
      commit('SET_USER_DATA', {
        id: response.data.user.id,
        name: response.data.user.name,
        // 必要なプロパティを明示的に指定
      });
    } else {
      // データが不完全な場合のハンドリング
      commit('SET_ERROR', 'ユーザーデータが見つかりませんでした。');
    }
  } catch (error) {
    // 3. エラー内容をコンソールに出力し、ユーザーにも通知する
    console.error('Login API error:', error);
    commit('SET_ERROR', 'ログイン処理中に予期しないエラーが発生しました。');
  }
}

また、ミューテーション側も受け取る引数が正しいか確認しておきましょう。

mutations: {
  SET_USER_DATA(state, userData) {
    // state.userが確実にuserDataオブジェクトになるようにする
    state.user = userData;
  },
  SET_ERROR(state, errorMessage) {
    state.error = errorMessage;
  }
}

[まとめ]

VuexやPiniaを用いた非同期処理のポイントは、「APIからの生データをそのまま信用しない」ことです。アクション内でしっかりとバリデーションを行い、正しい形式でコミットすることで、 undefined 起因のバグを劇的に減らすことができます

。非同期処理とエラーハンドリングを適切に組み合わせて、堅牢なフロントエンドを構築しましょう!

タイトルとURLをコピーしました