profile
viewpoint

Ask questionsSuggestion: add syntax or examples for nested slices

Currently redux-toolkit does not provide (or I couldn't find) an integrated way to define nested slices/reducers.

To split some slice logic into smaller slices, I'm currently doing something like this:

import { createSlice } from '@reduxjs/toolkit'

// import the reducer, initial state and relevant actions from the desired slice
import otherReducer, {
  complexAction,
  initialState as otherReducerInitialState
} from './otherReducer'

const myReducer = createSlice({
  name: 'myReducer',
  initialState: {
    someData: 'hello',
    // manually bind initial state
    someComplexData: otherReducerInitialState
  },
  reducers: {
    doStuff: (state, action) => {
      // ...
    },
  },
  extraReducers: {
    // manually bind each action
    [complexAction]: (state, action) => {
      // manually foward the state and action to the nested reducer
      state.someComplexData = otherReducer(state.someComplexData, action)
    },
  }
})

export const { doStuff } = myReducer.actions
export default myReducer.reducer

The above example works, but it's a lot of boilerplate code, and I can't shake the felling there should be a better way to describe nested slices.

reduxjs/redux-toolkit

Answer questions jessepinho

I made a combineSlices helper function to make it easy to adapt @agrinko's suggestion to any use case (note that it requires that you install reduce-reducers):

import { combineReducers } from "@reduxjs/toolkit";
import reduceReducers from "reduce-reducers";

/**
 * Combines a slice with any number of child slices.
 *
 * This solution is inspired by [this GitHub
 * comment](https://github.com/reduxjs/redux-toolkit/issues/259#issuecomment-604496169).
 *
 * @param sliceReducer - The reducer of the parent slice. For example, if your
 * slice is called `mySlice`, you should pass in `mySlice.reducer`.
 * @param {object} sliceInitialState - The initial state object passed to
 * `createSlice`. This is needed so that we know what keys are in the initial
 * state.
 * @param {object} childSliceReducers - An object of child slice reducers, keyed
 * by the name you want them to have in the Redux state namespace. For example,
 * if you've imported child slices called `childSlice1` and `childSlice2`, you
 * should pass in this argument as `{ childSlice1: childSlice1.reducer,
 * childSlice2: childSlice2.reducer }`. NOTE: The name of each child slice
 * should reflect its place in the namespace hierarchy to ensure that action
 * names are properly namespaced. For example, the `name` property of the
 * `childSlice1` slice should be `mySlice/childSlice1`, not just `childSlice1`.
 */
const combineSlices = (sliceReducer, sliceInitialState, childSliceReducers) => {
  const noopReducersFromInitialState = Object.keys(sliceInitialState).reduce(
    (prev, curr) => {
      return {
        ...prev,
        [curr]: (s = null) => s,
      };
    },
    {}
  );

  const childReducers = combineReducers({
    ...childSliceReducers,
    ...noopReducersFromInitialState,
  });

  return reduceReducers(sliceReducer, childReducers);
};

export default combineSlices;

Note that you need to pass it the initial state from the parent slice so that it knows what keys to make dummy no-op reducers for. Thus, here's an example of how it could be used:

import mySlice2 from "./mySlice2"
import mySlice3 from "./mySlice3"
import combineSlices from "./combineSlices"

// Note that `initialState` is defined as a variable before being passed
// to `createSlice` since it will need to also be passed to `combineSlices`.
const initialState = {
  count: 0,
}

const mySlice = createSlice({
  name: "mySlice",
  initialState,
  reducers: {
    setCount(state, action) {
      state.count = action.payload
    },
  }
})

export default combineSlices(mySlice.reducer, initialState, {
  mySlice2: mySlice2.reducer,
  mySlice3: mySlice3.reducer,
})
useful!
source:https://uonfu.com/
Github User Rank List