'Redux dispatch fires with correct payload, but state is not updated

I'm building a game using react-redux and TypeScript. I've got TS and React down, but redux... less so.

When the player clicks the start game button, the main menu is hidden via manipulation of redux state and the "gameState" is generated. "gameState" contains all the relevant information for the game world and entities therein, and is several thousand lines of serializable JSON. This part is definitely working, the problem comes when I try to dispatch to update it. I can see from the Redux browser extension that the payload being sent to the reducer function updateGameState is correct, but after the dispatch has been completed it's as if it never happened.

My question is simple: what am I doing wrong?

The code for the previously-mentioned dispatch is:

    let nGS = gameStateGenerator.create(scene)
    dispatch(updateGameState(nGS))

The layout of this part of the redux logic is as shown below. The four children of multiverse, universes, species, connections, and players, should all be populated, but are not.

layout of redux state tree

I'm using combined reducers, as follows. I've not used ES6 notation for the reducer object properties as part of my attempts to rule out causes (which hopefully speaks to my level of desperation).

store.ts (top level)

import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import gameState from '../features/gameState/gameState';

export const store = configureStore({
  reducer: {
    gameState: gameState
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

gameState.ts (first and so far only child of root)

import { combineReducers, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { GameState } from '../../interfaces/GameState';
import flags, { initialState as flagsInitialState} from '../flags/flags';
import multiverse, { initialState as multiverseInitialState } from '../multiverse/multiverse';

export const initialState: GameState = {
  multiverse: multiverseInitialState,
  flags: flagsInitialState
};

export const gameState = createSlice({
  name: 'gameState',
  initialState,
  reducers: {
    updateGameState: (state: GameState, action: PayloadAction<GameState>) => {
      return Object.assign({}, state, action.payload)
    }
  }
});

export const { updateGameState } = gameState.actions;

export default combineReducers({
  flags: flags,
  multiverse: multiverse
})

flags.ts

import { createSlice } from '@reduxjs/toolkit';
import { FlagsState } from '../../interfaces/Flags';

export const initialState: FlagsState = {
  ui: {
    showMainMenu: true,
    showWelcome: true,
    gameStarted: false,
    gameLoading: false,
    gameLoaded: false
  }
};

export const flags = createSlice({
  name: 'flags',
  initialState,
  reducers: {
    startGame: (state: Required<FlagsState>) => {
      return Object.assign({}, state, {
        ui: {
          ...state.ui,
          showMainMenu: false,
          gameStarted: true,
          gameLoading: true
        }
      })
    },
    gameLoaded: (state: Required<FlagsState>) => {
      return Object.assign({}, state, {
        ui: {
          ...state.ui,
          gameLoaded: true
        }
      })
    }
  }
});

export const { startGame, gameLoaded } = flags.actions;
export const getState = (state: FlagsState) => state;

export default flags.reducer;

And finally multiverse.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Multiverse } from '../../interfaces/Multiverse';
import { Universe } from './../../interfaces/Universes';

export const initialState: Multiverse = {
  universes: [],
  species: [],
  connections: [],
  players: []
};

export const multiverse = createSlice({
  name: 'multiverse',
  initialState,
  reducers: {
    setUniverses: (state: Required<Multiverse>, action: PayloadAction<Universe[]>) => {
      return Object.assign({}, state, { universes: action.payload })
    }
  }
});

export const { setUniverses } = multiverse.actions;
export const getState = (state: Multiverse) => state;

export default multiverse.reducer;


Solution 1:[1]

I'm think the trouble comes from your reducer, you use Object assign and return. Redux-Toolkit uses Immer to change the state with no mutating like this :

(state, action) => state.value = action.payload

See the doc, https://redux-toolkit.js.org/usage/immer-reducers#immutable-updates-with-immer

So for you, you can do something like :

state.univers = {...state.univers, ... action.payload}

with no return.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 LutherW