{"id":12233,"library":"typescript-fsa-reducers","title":"TypeScript FSA Reducers","description":"TypeScript FSA Reducers provides a fluent, typesafe API for defining Redux reducers. It builds on `typescript-fsa` to streamline reducer creation by removing common boilerplate such as `if-else` chains for action type checking and manual extraction of payloads from actions. Instead, it offers a method-chaining approach with dedicated handlers for specific action creators. The current stable version is 1.2.2. While there isn't a strictly defined release cadence, the library has demonstrated consistent updates and a commitment to stability, notably reaching version 1.0.0. Its key differentiators include the fluent builder pattern (`.case()`, `.default()`, `.withHandling()`, `.build()`), strong type inference for state and payloads, and direct integration with `typescript-fsa`'s action creators, making reducer logic concise and highly maintainable in TypeScript Redux applications.","status":"active","version":"1.2.2","language":"javascript","source_language":"en","source_url":"git://github.com/dphilipson/typescript-fsa-reducers","tags":["javascript","redux","typescript","action","reducer","builder"],"install":[{"cmd":"npm install typescript-fsa-reducers","lang":"bash","label":"npm"},{"cmd":"yarn add typescript-fsa-reducers","lang":"bash","label":"yarn"},{"cmd":"pnpm add typescript-fsa-reducers","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Core dependency for creating typesafe actions that this library consumes. It became a peer dependency in v1.0.0.","package":"typescript-fsa","optional":false}],"imports":[{"note":"This is the primary way to start a reducer chain when an initial state is always provided. The library is primarily designed for modern ES Modules and TypeScript.","wrong":"const { reducerWithInitialState } = require('typescript-fsa-reducers');","symbol":"reducerWithInitialState","correct":"import { reducerWithInitialState } from 'typescript-fsa-reducers';"},{"note":"Used to create a reducer builder without an initial state, treating `undefined` as a regular state value. It's a named export, not a default.","wrong":"import reducerWithoutInitialState from 'typescript-fsa-reducers';","symbol":"reducerWithoutInitialState","correct":"import { reducerWithoutInitialState } from 'typescript-fsa-reducers';"},{"note":"Used for more advanced reducer composition scenarios involving state type upcasting. Follows standard named import conventions.","wrong":"const upcastingReducer = require('typescript-fsa-reducers').upcastingReducer;","symbol":"upcastingReducer","correct":"import { upcastingReducer } from 'typescript-fsa-reducers';"}],"quickstart":{"code":"import actionCreatorFactory from 'typescript-fsa';\nimport { reducerWithInitialState } from 'typescript-fsa-reducers';\n\nconst actionCreator = actionCreatorFactory();\n\ninterface State {\n    name: string;\n    balance: number;\n    isFrozen: boolean;\n}\n\nconst INITIAL_STATE: State = {\n    name: 'Untitled',\n    balance: 0,\n    isFrozen: false,\n};\n\nconst setName = actionCreator<string>('SET_NAME');\nconst addBalance = actionCreator<number>('ADD_BALANCE');\nconst setIsFrozen = actionCreator<boolean>('SET_IS_FROZEN');\n\nconst myReducer = reducerWithInitialState(INITIAL_STATE)\n    .case(setName, (state, name) => ({ ...state, name }))\n    .case(addBalance, (state, amount) => ({\n        ...state,\n        balance: state.balance + amount,\n    }))\n    .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }))\n    .default((state, action) => {\n        // Handle any unhandled actions or simply return the state.\n        console.log(`Unhandled action: ${action.type}`);\n        return state;\n    })\n    .build(); // Don't forget to call .build() to get the final reducer function\n\n// Example usage (typically with Redux store)\nlet currentState = myReducer(undefined, { type: '@@INIT' }); // Initial state\nconsole.log('Initial state:', currentState); // { name: 'Untitled', balance: 0, isFrozen: false }\n\ncurrentState = myReducer(currentState, setName('Alice'));\nconsole.log('After setName:', currentState); // { name: 'Alice', balance: 0, isFrozen: false }\n\ncurrentState = myReducer(currentState, addBalance(100));\nconsole.log('After addBalance:', currentState); // { name: 'Alice', balance: 100, isFrozen: false }\n\ncurrentState = myReducer(currentState, { type: 'UNKNOWN_ACTION' });\nconsole.log('After UNKNOWN_ACTION:', currentState); // { name: 'Alice', balance: 100, isFrozen: false } (and console log for unhandled action)","lang":"typescript","description":"Demonstrates defining a typesafe Redux reducer using `reducerWithInitialState`, handling different action types with `.case()`, adding a default handler, and calling `.build()` to finalize the reducer. It also shows basic reducer usage."},"warnings":[{"fix":"Ensure `typescript-fsa` is explicitly listed and installed in your project's `dependencies` or `devDependencies`: `npm install typescript-fsa` or `yarn add typescript-fsa`.","message":"In `v1.0.0`, `typescript-fsa` transitioned from a hard dependency to a peer dependency. If your project relied on `typescript-fsa-reducers` to implicitly install `typescript-fsa`, you must now install `typescript-fsa` explicitly.","severity":"breaking","affected_versions":">=1.0.0"},{"fix":"Always terminate your reducer chain with `.build()`. For example, `const reducer = reducerWithInitialState(...).case(...).build();`.","message":"In `v0.4.0`, reducer chains became mutable. Previously, methods like `.case()` returned a new reducer builder instance, but now they modify the existing instance. To obtain an immutable reducer function (which is the standard for Redux), you *must* call the new `.build()` method at the end of your chain.","severity":"breaking","affected_versions":">=0.4.0"},{"fix":"When using `reducerWithoutInitialState`, ensure that the first argument passed to the generated reducer is a valid state value or explicitly type your state to include `undefined` if that is intended (though `reducerWithInitialState` is typically preferred for explicit initial states). The library expects `undefined` to trigger initial state logic only for `reducerWithInitialState`.","message":"Starting with `v1.2.2`, TypeScript types were updated for `reducerWithoutInitialState`. Passing `undefined` as the *first* argument (representing the state) to reducers created by `reducerWithoutInitialState` will now correctly result in a type error unless `undefined` is explicitly a subtype of your state. This corrects a previous typing oversight.","severity":"gotcha","affected_versions":">=1.2.2"},{"fix":"Be aware that reducers correctly handle `undefined` as a state argument (especially for `reducerWithInitialState`) to trigger initial state usage. Ensure your code accounts for this and doesn't rely on previous type errors for `undefined` state handling.","message":"In `v0.4.5`, the type definition of reducers created by builders was updated to explicitly accept `undefined` as its first argument (the state), which causes the initial state to be used (when using `reducerWithInitialState`). While this functionality always existed, the type definitions previously 'hid' it. This change aligns with Redux type definitions.","severity":"gotcha","affected_versions":">=0.4.5"},{"fix":"For complex type interactions, ensure explicit type annotations for state, use `upcastingReducer()` as needed, or review the `typescript-fsa` and Redux documentation on reducer composition and type inference for best practices.","message":"When combining reducers that might have differing or upcasted state types, you may encounter TypeScript errors. The `.build()` method helps to normalize the output type, but complex scenarios might still require careful type management or use of `upcastingReducer`.","severity":"gotcha","affected_versions":">=0.4.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Always call `.build()` at the end of your reducer chain to get the actual reducer function: `const myReducer = reducerWithInitialState(...).case(...).build();`","cause":"Prior to v0.4.0, reducer builder chains were immutable and could be used directly as Redux reducers. After v0.4.0, they became mutable builders, and the final reducer function must be explicitly extracted.","error":"TypeError: reducer is not a function"},{"fix":"Install `typescript-fsa` explicitly: `npm install typescript-fsa` or `yarn add typescript-fsa`.","cause":"From v1.0.0, `typescript-fsa` is a peer dependency and must be installed separately by the consumer project. If it's missing, TypeScript will report that the module cannot be found.","error":"Cannot find module 'typescript-fsa' or its corresponding type declarations."},{"fix":"If you intend for `undefined` to trigger an initial state, use `reducerWithInitialState(INITIAL_STATE)`. If you truly want to handle `undefined` as a valid state, ensure your `State` type explicitly includes `| undefined`.","cause":"This error can occur when trying to use a reducer created by `reducerWithoutInitialState()` and passing `undefined` as the state, particularly with `v1.2.2`'s stricter type definitions. `reducerWithoutInitialState` treats `undefined` as a regular state value, not a trigger for initial state.","error":"Argument of type 'undefined' is not assignable to parameter of type 'State'."}],"ecosystem":"npm"}