Redux の getState() で状態が取得できない問題と解決策
問題の概要
Redux において createStore()
が非推奨となり、代わりに @reduxjs/toolkit
の configureStore()
の使用が推奨されています。この変更に伴い、アクション内で getState()
を使用して状態を取得しようとすると、以下のような問題が発生することがあります:
export const addProduct = product => async (dispatch, getState) => {
try {
const { userLogin: { userInfo } } = getState() // undefined が返される
// ...
} catch (error) {
// エラーハンドリング
}
}
getState()
から userInfo
が undefined
として返され、認証トークンの取得に失敗します。しかし、getState()
を削除するとアクションは正常に動作します。
根本原因
この問題は createStore()
の非推奨化自体が直接の原因ではありません。Redux メンテナーによると、createStore
の非推奨表示はエディター上の視覚的な指標であり、ランタイムの警告ではないと説明されています。
実際の問題は、ストアの設定方法とミドルウェアの設定に関連しています。
解決策
1. Redux Toolkit の正しい設定方法
configureStore
を使用する際は、ミドルウェアの設定を適切に行う必要があります:
import { configureStore } from '@reduxjs/toolkit'
import thunk from 'redux-thunk'
// 各Reducerのインポート
const reducer = {
userLogin: userLoginReducer,
// その他のReducer
}
const userInfoFromStorage = localStorage.getItem('userInfo')
? JSON.parse(localStorage.getItem('userInfo'))
: null
const preloadedState = {
userLogin: { userInfo: userInfoFromStorage },
}
const store = configureStore({
reducer,
preloadedState, // 正しいスペル: preloadedState (not preLoadedState)
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(thunk),
})
export default store
注意
preLoadedState
ではなく preloadedState
(Lが小文字)が正しいプロパティ名です。このスペルミスが状態の初期化に影響を与える可能性があります。
2. モダンな Redux への移行
Redux Toolkit を使用したより現代的なアプローチ:
// store.js
import { configureStore } from '@reduxjs/toolkit'
import { userLoginReducer } from './reducers/userReducers'
export const makeStore = () => {
return configureStore({
reducer: {
userLogin: userLoginReducer,
// その他のReducer
},
preloadedState: {
userLogin: {
userInfo: localStorage.getItem('userInfo')
? JSON.parse(localStorage.getItem('userInfo'))
: null
}
},
// middlewareは自動的に設定される(thunkを含む)
})
}
3. createStore のレガシーな使用法(非推奨)
どうしても従来の createStore
を使用する必要がある場合:
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(
reducer,
preloadedState,
composeWithDevTools(applyMiddleware(thunk))
)
非推奨
この方法はレガシーなアプローチであり、新規プロジェクトでは推奨されません。Redux Toolkit を使用することが公式推奨です。
ベストプラクティス
モダンなアクションの書き方
Redux Toolkit では createAsyncThunk
を使用することで、より簡潔に非同期アクションを記述できます:
import { createAsyncThunk } from '@reduxjs/toolkit'
export const addProduct = createAsyncThunk(
'products/add',
async (product, { getState }) => {
const { userLogin: { userInfo } } = getState()
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
}
const response = await axios.post('/product', product, config)
return response.data
}
)
ストア設定の完全な例
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './userSlice'
import productReducer from './productSlice'
const store = configureStore({
reducer: {
user: userReducer,
product: productReducer,
},
preloadedState: {
user: {
userInfo: localStorage.getItem('userInfo')
? JSON.parse(localStorage.getItem('userInfo'))
: null
}
},
devTools: process.env.NODE_ENV !== 'production',
})
export default store
結論
getState()
が undefined
を返す問題は、主に以下の原因が考えられます:
- ストア設定の不備:
preloadedState
のスペルミスや不適切な設定 - ミドルウェア設定の問題:thunk ミドルウェアが正しく設定されていない
- 状態構造の不一致:
getState()
で想定している状態構造と実際の構造が異なる
Redux Toolkit の configureStore
を使用し、公式ドキュメントに従った適切な設定を行うことで、これらの問題を解決できます。新しいプロジェクトでは、常に Redux Toolkit を採用し、モダンな Redux パターンを実践することをお勧めします。