npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

redux-named-reducer

v3.2.0

Published

redux helper lib to automatically assign name to your reducer fromslice name

Readme

redux-named-reducer

A small and lightweight TypeScript utility for Redux that lets you attach names directly to reducers and automatically combine them into a fully typed Redux reducer map.

It removes the need to manually maintain reducer keys when using combineReducers while preserving the actual reducer names in TypeScript.

🚀 Key Features

  • Named Reducers: Attach a sliceName directly to any Redux reducer.
  • Type-Safe Reducer Names: Preserves reducer names as TypeScript literal types such as "login" instead of widening them to string.
  • Automatic Reducer Mapping: Automatically creates the reducer object required by combineReducers.
  • Redux Toolkit Compatible: Works with standard Redux reducers and createSlice.
  • Strong Type Inference: The resulting root reducer preserves the state type for each named reducer.
  • Simple API: Provides a small and easy-to-use API.
  • Dynamic Reducer Support: Easily collect and combine named reducers from different parts of your application.

📦 Installation

Install the package using npm:

npm install redux-named-reducer

Or using yarn:

yarn add redux-named-reducer

Or using pnpm:

pnpm add redux-named-reducer

🛠️ Step-by-Step Usage

1. Create a Named Reducer

Use createReduxNamedReducer to attach a name to any normal Redux reducer.

import { createReduxNamedReducer } from "redux-named-reducer";

const counterReducer = (state = 0, action: { type: string }) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;

    case "DECREMENT":
      return state - 1;

    default:
      return state;
  }
};

const namedCounterReducer = createReduxNamedReducer(counterReducer, "counter");

The reducer now contains the sliceName:

console.log(namedCounterReducer.sliceName);

// "counter"

The "counter" name is also preserved as a TypeScript literal type.

// typeof namedCounterReducer.sliceName
// "counter"

2. Using With Redux Toolkit createSlice

You can use createReduxNamedReducer directly with a reducer created using Redux Toolkit's createSlice.

First, create your normal Redux Toolkit slice:

import { createSlice } from "@reduxjs/toolkit";

export const loginSlice = createSlice({
  name: "login",

  initialState: {
    pending: false,
    success: null,
    error: null,
  },

  reducers: {},

  extraReducers: (builder) => {
    builder
      .addCase(initiateLogin.pending, (state) => {
        state.pending = true;
      })

      .addCase(initiateLogin.fulfilled, (state, action) => {
        state.pending = false;
        state.success = action.payload;
      })

      .addCase(initiateLogin.rejected, (state, action) => {
        state.pending = false;
        state.error = action.error.message ?? "Unknown Error";
      });
  },
});

Now pass the slice reducer and slice name to createReduxNamedReducer:

import { createReduxNamedReducer } from "redux-named-reducer";

export const loginSliceReducer = createReduxNamedReducer(
  loginSlice.reducer,
  loginSlice.name,
);

Because loginSlice.name is "login", the resulting reducer keeps that name:

loginSliceReducer.sliceName;

// "login"

This also means TypeScript can correctly infer the reducer key when it is later passed to combineNamedSlices.


3. Create Multiple Named Reducers

You can name each reducer when defining your application's state modules.

import { createReduxNamedReducer } from "redux-named-reducer";

const authReducer = createReduxNamedReducer(authReducerImplementation, "auth");

const userReducer = createReduxNamedReducer(userReducerImplementation, "user");

const settingsReducer = createReduxNamedReducer(
  settingsReducerImplementation,
  "settings",
);

Each reducer now knows which key it should use inside the Redux state.

authReducer.sliceName;
// "auth"

userReducer.sliceName;
// "user"

settingsReducer.sliceName;
// "settings"

The names are preserved as literal TypeScript types rather than being converted to a generic string.


🧩 Combining Named Reducers

Use combineNamedSlices to combine your named reducers.

import { combineNamedSlices } from "redux-named-reducer";

const rootReducer = combineNamedSlices(
  authReducer,
  userReducer,
  settingsReducer,
);

The library automatically creates a reducer map equivalent to:

{
  auth: authReducer,
  user: userReducer,
  settings: settingsReducer,
}

You don't need to manually specify the reducer keys.


🔷 Type-Safe Root State

One of the main benefits of redux-named-reducer is that reducer names are preserved by TypeScript.

For example:

const loginReducer = createReduxNamedReducer(loginSlice.reducer, "login");

const userReducer = createReduxNamedReducer(userSlice.reducer, "user");

const rootReducer = combineNamedSlices(loginReducer, userReducer);

You can then create your root state type:

export type RootState = ReturnType<typeof rootReducer>;

TypeScript will infer a structure similar to:

{
  login: LoginState;
  user: UserState;
}

Instead of losing the reducer names and getting an index signature such as:

{
  [key: string]: unknown;
}

This makes the resulting reducer useful with strongly typed Redux applications.


🏪 Using With Redux Toolkit Store

You can directly use the generated reducer with configureStore.

import { configureStore } from "@reduxjs/toolkit";
import {
  combineNamedSlices,
  createReduxNamedReducer,
} from "redux-named-reducer";

const counterReducer = createReduxNamedReducer(
  counterReducerImplementation,
  "counter",
);

const userReducer = createReduxNamedReducer(userReducerImplementation, "user");

const rootReducer = combineNamedSlices(counterReducer, userReducer);

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

export type RootState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

Your Redux state will now follow the reducer names:

{
  counter: ...,
  user: ...,
}

🧩 Working With Existing Reducers

You don't need to change the implementation of an existing reducer.

Simply pass it to createReduxNamedReducer:

import { createReduxNamedReducer } from "redux-named-reducer";

import { existingUserReducer } from "./userReducer";

export const userReducer = createReduxNamedReducer(existingUserReducer, "user");

The input can be any normal Redux Reducer.

Reducer<State, Action>;

The returned value becomes:

Reducer<State, Action> & {
  sliceName: "user";
}

🔄 Dynamic Reducer Collection

Named reducers can also be collected and combined dynamically.

const reducers = [
  authReducer,
  userReducer,
  settingsReducer,
  notificationReducer,
];

const rootReducer = combineNamedSlices(...reducers);

You don't have to manually create:

combineReducers({
  auth: authReducer,
  user: userReducer,
  settings: settingsReducer,
  notification: notificationReducer,
});

The sliceName from each reducer is used automatically.


🔷 TypeScript Support

The package provides the TReduxNamedReducer type.

The reducer name is a generic type parameter:

import { Reducer } from "@reduxjs/toolkit";

export type TReduxNamedReducer<
  S = any,
  A extends { type: string } = { type: string },
  N extends string = string,
> = Reducer<S, A> & {
  sliceName: N;
};

The third generic parameter represents the reducer name.

For example:

TReduxNamedReducer<LoginState, LoginAction, "login">;

represents:

Reducer<LoginState, LoginAction> & {
  sliceName: "login";
}

Type-Safe createReduxNamedReducer

The createReduxNamedReducer function accepts a normal Redux reducer and returns a named reducer.

export const createReduxNamedReducer = <
  S,
  A extends { type: string },
  N extends string,
>(
  target: Reducer<S, A>,
  sliceName: N,
): TReduxNamedReducer<S, A, N> => {
  return Object.assign(target, {
    sliceName,
  });
};

Because N is inferred from the provided name, the name is preserved.

const reducer = createReduxNamedReducer(loginSlice.reducer, "login");

TypeScript understands the result as:

TReduxNamedReducer<LoginState, LoginAction, "login">;

Custom State and Action Types

You can also provide your own state and action types.

import { TReduxNamedReducer } from "redux-named-reducer";

type CounterState = {
  value: number;
};

type CounterAction =
  | {
      type: "INCREMENT";
    }
  | {
      type: "DECREMENT";
    };

const counterReducer: TReduxNamedReducer<
  CounterState,
  CounterAction,
  "counter"
> = (state = { value: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {
        value: state.value + 1,
      };

    case "DECREMENT":
      return {
        value: state.value - 1,
      };

    default:
      return state;
  }
};

🛠️ API

createReduxNamedReducer

Creates a named Redux reducer from any normal Redux reducer.

createReduxNamedReducer(target, sliceName);

Parameters

  • target — The normal Redux reducer.
  • sliceName — The name that should be used for the reducer in the Redux state.

Example

const userReducer = createReduxNamedReducer(existingUserReducer, "user");

The returned reducer contains:

userReducer.sliceName;

// "user"

combineNamedSlices

Combines multiple named reducers into a single reducer.

combineNamedSlices(...reducers);

Example

const rootReducer = combineNamedSlices(
  authReducer,
  userReducer,
  settingsReducer,
);

This is equivalent to:

combineReducers({
  auth: authReducer,
  user: userReducer,
  settings: settingsReducer,
});

The difference is that the reducer keys are automatically derived from sliceName and preserved in TypeScript.


createReduxNamedReducerMap

Creates a reducer map from named reducers.

createReduxNamedReducerMap(reducers);

Example

const reducerMap = createReduxNamedReducerMap([
  authReducer,
  userReducer,
  settingsReducer,
]);

Result:

{
  auth: authReducer,
  user: userReducer,
  settings: settingsReducer,
}

The resulting map preserves the reducer names as typed keys.

This utility is also used internally by combineNamedSlices.


⚠️ Important Note

createReduxNamedReducer uses Object.assign to add sliceName to the reducer:

Object.assign(target, {
  sliceName,
});

This means the original reducer is modified and the same reducer reference is returned.

const namedReducer = createReduxNamedReducer(reducer, "users");

console.log(namedReducer === reducer);

// true

No new reducer function is created.


📚 Complete Example

Here is a complete example using Redux Toolkit and createSlice.

Login Slice

import { createSlice } from "@reduxjs/toolkit";

type LoginState = {
  pending: boolean;
  success: unknown;
  error: string | null;
};

const initialState: LoginState = {
  pending: false,
  success: null,
  error: null,
};

export const loginSlice = createSlice({
  name: "login",

  initialState,

  reducers: {},

  extraReducers: (builder) => {
    builder
      .addCase(initiateLogin.pending, (state) => {
        state.pending = true;
      })

      .addCase(initiateLogin.fulfilled, (state, action) => {
        state.pending = false;
        state.success = action.payload;
      })

      .addCase(initiateLogin.rejected, (state, action) => {
        state.pending = false;
        state.error = action.error.message ?? "Unknown Error";
      });
  },
});

Create the Named Reducer

import { createReduxNamedReducer } from "redux-named-reducer";

export const loginReducer = createReduxNamedReducer(
  loginSlice.reducer,
  loginSlice.name,
);

Create Another Named Reducer

export const userReducer = createReduxNamedReducer(
  userSlice.reducer,
  userSlice.name,
);

Combine Them

import { combineNamedSlices } from "redux-named-reducer";

export const rootReducer = combineNamedSlices(loginReducer, userReducer);

Create the Store

import { configureStore } from "@reduxjs/toolkit";

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

Create the Root State Type

export type RootState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

The resulting state is strongly typed:

{
  login: LoginState;
  user: UserState;
}

You can now safely access:

const loginState = state.login;

const isPending = state.login.pending;

const user = state.user;

🎯 Why Use redux-named-reducer?

Without this package, you normally need to manually maintain the relationship between reducer names and reducer instances:

combineReducers({
  login: loginSlice.reducer,
  user: userSlice.reducer,
  settings: settingsSlice.reducer,
});

With redux-named-reducer, the reducer carries its own name:

const loginReducer = createReduxNamedReducer(
  loginSlice.reducer,
  loginSlice.name,
);

const userReducer = createReduxNamedReducer(userSlice.reducer, userSlice.name);

Then combining them becomes:

const rootReducer = combineNamedSlices(loginReducer, userReducer);

This keeps the reducer name and reducer implementation together while preserving strong TypeScript inference.


📄 License

MIT