How can I extend cases in reducers with Redux ToolKit?

Viewed 661

I am making a plugin where there are some universal actions that can be handled in a reducer. I am unsure how to make it so someone can extend for additional cases with the same action. I have some code like this:

export default function createStore() {
  return configureStore({
    preloadedState: initialStoreState,
    reducer: {
      workingStores: workingStoreReducer,
    },
  });
}

The reducer is what you would expect:

export default createReducer(initialWorkingStoreState, (builder) => {
  builder
    // chained for a number of actions rather than just one in reality
    .addCase(anImportedAction, (state, { payload }) => {}); 

Essentially someone using my plugin may have additional actions to modify the state of workingStores.

I am thinking of solving the problem in a seemingly complicated way where createStore could take a callback, and then my workingStoreReducer becomes a method of getWorkingStoreReducer which would take that callback...

export default function getWorkingStoreReducer(extensions) {
  return createReducer(initialWorkingStoreState, (builder) => {
    exensions(builder); // method would add cases to builder
    builder.addCase(anImportedAction, (state, { payload }) => {}); 

  });
}

This seems convoluted so hoping there is a simpler thing I have missed in the docs

2 Answers

I ended up using the extension method I proposed in OG question. I couldn't find a significantly different answer.

My Reducer:

export default function getReducer(extension?: Function) {
  return createReducer<number | null>(null, (builder) => {
    if (typeof extension === 'function') {
      extension(builder);
    }

    builder
      .addCase(eventNewDocAction, (_, { payload }) => {
        const { workingDocumentId } = payload;

        return workingDocumentId;
      })
      .addCase(
        eventCloseDocAction,
        (_, { payload }) => payload.workingDocumentId,
      );
  });
}

My method that gets passed as extension to the above


export default function (builder) {
  builder.addCase(eventGenericAction, (state, { payload }) => {
    const { documentData, evt } = payload;
    const { workingDocumentId } = documentData;
    const evtName = evt.name as EventType;

    if (activeDocumentEvents.has(evtName)) {
      return workingDocumentId;
    }

    // noop
    return state;
  });
}

Then for my reducer key in configureStore I have this:

// extension.workingDocumentId is the above method that adds eventGenericAction
workingDocumentId: getWorkingDocumentIdReducer(
        extensions.workingDocumentId ?? undefined,
      ),

Is composing the builder callback ahead of time an option?

// Your code
export const createBuilder = (additionalCases = []) => builder => {
    builder.addCase(anImportedAction, (state, { payload }) => {})
        .addCase(anImportedAction, (state, { payload }) => {})

    additionalCases.forEach(([action, reducer]) => builder.addCase(action, reducer)
}

const workingStoreReducer = createReducer(initialWorkingState, createBuilder());

Elsewhere, extend cases and rebuild the reducer

const builderCallback = createBuilder([
  [anExtendedAction, (state, { payload }) => {}],
  [anExtendedAction, (state, { payload }) => {}],
]);
const workingStoreReducer = createReducer(workingState, builderCallback);

// Replace store reducer
Related