How to build a Redux + ReduxToolkit store with a repeating nested slice?

Viewed 271

Let's say my state looks something like this (taken from https://redux.js.org/usage/structuring-reducers/normalizing-state-shape):

{
    posts : {
        byId : {
            "post1" : {
                id : "post1",
                author : "user1",
                body : "......",
                comments : ["comment1", "comment2"]
            },
            "post2" : {
                id : "post2",
                author : "user2",
                body : "......",
                comments : ["comment3", "comment4", "comment5"]
            }
        },
        allIds : ["post1", "post2"]
    },
    comments : {
        byId : {
            "comment1" : {
                id : "comment1",
                author : "user1",
                comment : ".....",
            },
            "comment2" : {
                id : "comment2",
                author : "user2",
                comment : ".....",
            }
        },
        allIds : ["comment1", "comment2"]
    },
    users : {
        byId : {
            "user1" : {
                username : "user1",
                name : "User 1",
            },
            "user2" : {
                username : "user2",
                name : "User 2",
            }
        },
        allIds : ["user1", "user2"]
    }
}

And accordingly I've created 3 slices using Redux-Toolkit's createSlice: postsSlice, commentsSlice, usersSlice. This is simple enough.

But now I want to add another layer to the state to keep track of blogs, and so the whole structure above needs to be nested and repeated for each blog:

{
    blogs: {
        byId: {
            "blog1": {
                posts: {...},
                comments: {...},
                users: {...}
            },
            "blog2": {
                posts: {...},
                comments: {...},
                users: {...}
            },
        },
        allIds: ["blog1", "blog2"]
    }
}

Now I need to use createSlice to create a new slice, blogsSlice. But for each blog item in this slice I need a sub-state that is comprised of the other 3 slices.

How do I combine the slices/reducers in a way that will actually create this structure? How should I define the initial state and reducer functions that I pass to createSlice in order to make this structure work?

1 Answers

I actually put together a gist demonstrating something very similar to this, for a notional ChatRoom > ChatMessages structure. This may not be exactly what you're looking for, but hopefully it will give you an idea of how you can structure things that will server as an inspiration.

Pasting the entire snippet here as an example:

// Example of using multiple / nested `createEntityAdapter` calls 
// within a single Redux Toolkit slice

interface Message {
  id: string;
  roomId: string;
  text: string;
  timestamp: string;
  username: string;
}

interface ChatRoomEntry {
  id: string;
  messages: EntityState<Message>;
}

const roomsAdapter = createEntityAdapter<ChatRoomEntry>();
const messagesAdapter = createEntityAdapter<Message>();

const fetchRooms = createAsyncThunk(
  "chats/fetchRooms",
  chatsAPI.fetchRooms
);

const fetchMessages = createAsyncThunk(
  "chats/fetchMessages",
  async (roomId) => {
    return chatsAPI.fetchMessages(roomId);
  }
)

const chatSlice = createSlice({
  name: "chats",
  initialState: roomsAdapter.getInitialState(),
  reducers: {
  
  },
  extraReducers: builder => {
    builder.addCase(fetchRooms.fulfilled, (state, action) => {
      const roomEntries = action.payload.map(room => {
        return {id: room.id, messages: messagesAdapter.getInitialState()};
      });
      
      roomsAdapter.setAll(state, roomEntries);
    })
    .addCase(fetchMessages.fulfilled, (state, action) => {
      const roomId = action.meta.arg;
      const roomEntry = state.entities[roomId];
      if (roomEntry) {
        messagesAdapter.setAll(roomEntry.messages, action.payload);
      }
    })
  }
})

/*
Resulting state:
{
  ids: ["chatRoom1"],
  entities: {
    chatRoom1: {
      id: "chatRoom1",
      messages: {
        ids: ["message1", "message2"],
        entities: {
          message1: {id: "message1", text: "hello"},
          message2: {id: "message2", text: "yo"},
        }
      }
    }
  }
}
*/
Related