I am encountering a problem with my Redux setup in React Native where I have two reducers: one for authentication and another for menu. While the auth reducer persists data across the entire app without any issues, the menu reducer seems to have a problem. After the getMenu()
action is called, the menu state initially populates data correctly, but then immediately becomes undefined. I’m seeking advice on how to troubleshoot and resolve this issue. I want to be able to persist the menu data throughout the entire application.
Code:
//store.js
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import {
persistStore,
persistReducer,
} from "redux-persist";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { combineReducers } from "redux";
import authReducer from "./auth/authReducer";
import menuReducer from "./menu/menuReducer";
const rootPersistConfig = {
key: "root",
storage: AsyncStorage,
keyPrefix: "redux-",
whitelist: [],
};
const rootReducer = combineReducers({
auth: authReducer,
menu: menuReducer,
});
const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
export const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
reducer: persistedReducer,
});
export const persistor = persistStore(store);
//menuReducer.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
menu: null,
loading: false,
error: null,
};
const menuSlice = createSlice({
name: "menu",
initialState,
reducers: {
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
state.loading = false;
},
setMenu: (state, action) => {
state.menu = action.payload;
state.loading = false;
},
clearMenu: (state) => {
state.menu = null;
},
clearError: (state) => {
state.error = null;
},
},
extraReducers: (builder) => {
builder
.addCase("menu/getMenu/pending", (state) => {
state.loading = true;
})
.addCase("menu/getMenu/fulfilled", (state, action) => {
state.loading = false;
state.menu = action.payload;
})
.addCase("menu/getMenu/rejected", (state, action) => {
state.loading = false;
state.error = action.payload;
});
},
});
export const { setLoading, setError, setMenu, clearMenu, clearError } =
menuSlice.actions;
export default menuSlice.reducer;
Here is how i update the menu state with data:
//menuAction.js
export const getMenu = createAsyncThunk(
"menu/getMenu",
async ({ storeNumber, token }, { dispatch }) => {
try {
dispatch(setLoading(true));
const menu = await GetMenuApi(storeNumber, token);
dispatch(setMenu(menu));
dispatch(clearError());
dispatch(setLoading(false));
} catch (error) {
dispatch(setError(error));
}
}
);
//App.js
export default function App(){
const { menu } = useSelector((state) => state.menu);
const dispatch = useDispatch();
const storeNumber = 1;
const token = 'abc'
console.log(menu) //logs correct data then immediately logs undefined
const getMenuFunc= () => {
dispatch(getMenu({ storeNumber, token }));
};
return(
<View style={{flex:1}}>
<Button onPress={getMenuFunc} title="get menu"/>
</View>
);
}
After invoking the getMenuFunc()
function, the console.log(menu)
correctly logs the menu data the first time. However, upon subsequent invocations of getMenuFunc()
, the menu
variable becomes undefined
. Interestingly, upon app reload via Expo and subsequent invocation of getMenuFunc()
, the data is retrieved again, only to become undefined
once more.
4
Answers
Have you wrapped your root component with PersistGate? if not then you need to wrap it because it delays the rendering of your app’s UI until your persisted state has been retrieved and saved to redux. otherwise, your app will load before the state is saved to the redux store hence you will get undefined when you try to access the state because it does not exist on the store.
Please check the link below for more https://www.npmjs.com/package/redux-persist#:~:text=If%20you%20are%20using%20react%2C%20wrap%20your%20root%20component%20with%20PersistGate.%20This%20delays%20the%20rendering%20of%20your%20app%27s%20UI%20until%20your%20persisted%20state%20has%20been%20retrieved%20and%20saved%20to%20redux.%20NOTE%20the%20PersistGate%20loading%20prop%20can%20be%20null%2C%20or
First of all, I think that there is a confusion of RTK usage 🙂
In your slice, under the
reducers
key, you’re re-inventing the wheel of theextraReducer
+createAsyncThunk
1st solution (not the best IMO) : don’t use
createAsyncThunk
You can write your thunk like that.
If you choose this solution, you can also remove
extraReducers
.The issue is that action dispatched by createAsyncThunk are in conflict with the one you defined
2nd solution : RTK for the win
If you check the doc of createAsyncThunk, you’ll see that 3 action are generated => the one that you defined under
extraReducers
key. But more important,menu/getMenu/pending
is dispatched directly when the thunk is dispatched andmenu/getMenu/fulfilled
when there was no error.With that in mind, you have a conflict
So we’ll have the following code
Issue
Your
getMenu
action isn’t returning any payload value, so thesetMenu
action is dispatched and the state is updated with themenu
value, only to be wiped out by thegetMenu.fulfilled
action that has an undefined payload value.You are making more work for yourself though. Just use the thunk actions directly and ensure you properly return values from your
getMenu
action.Solution
Update
getMenu
to correctly return the menu as a resolved/fulfilled payload. Do the same thing for errors.Example:
Async thunks should be used for handling asynchronous logic such as making API calls. Dispatching actions should be handled in the "extrareducers" section of the slice where you define how your state should respond to different actions.