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 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-actions-ts-reducer

v0.2.0

Published

Helps You to write reducers for redux-action actions in TypeScript in type-safe manner and without needing to specify excessive type information - everything is inferred based on types of redux-action actions and initial state

Downloads

173

Readme

CircleCI Build Status npm

redux-actions-ts-reducer

Helper for writing type-safe and bloat-free reducers using redux-actions and TypeScript

Helps You to write reducers for redux-actions actions in TypeScript in type-safe manner and without needing to specify excessive type information - everything is inferred based on initial state and types of redux-action action creators.

Table of Contents

Getting Started

Prerequisites

Assuming that you have already installed

  1. redux-actions (since this library uses internally handleActions(previouslyAddedReducersAsMap) from redux-actions and can infer reducer action.payload type based on action creator type that is created with createAction<actionPayloadType>(...))
  2. TypeScript type declarations @types/redux-actions (since using this libary makes most sense with TypeScript that can detect problems at compile-time)

Installation

$ npm install redux-actions-ts-reducer --save

or

$ yarn add redux-actions-ts-reducer

Usage

No boilerblate, type-safe

See comments in the example code bellow:

import { ReducerFactory } from 'redux-actions-ts-reducer';
import { createAction } from 'redux-actions';

// Sample action creators(redux-actions) and action type constants that reducer can handle
const negate = createAction('NEGATE'); // action without payload
const add = createAction<number>('ADD'); // action with payload type `number`
const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE'; // could be useful when action type like this is defined by 3rd party library
const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE'; // could be useful when action type like this is defined by 3rd party library

// type of the state
class SampleState {
	count = 0;
	message: string = null;
}

// creating reducer that combines several reducers
const reducer = new ReducerFactory(new SampleState())
	// `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
	// Type of `action.payload` is inferred based on first argument (action creator)
	.addReducer(add, (state, action) => {
		return {
			...state,
			count: state.count + action.payload,
		};
	})
	// no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
	.addReducer(negate, (state) => {
		return {
			...state,
			count: state.count * -1,
		};
	})
	// when adding reducer for action using string actionType
	// (using `addReducer(actionType: string, reducerFunction)` instead of `redux-actions` actionCreator, that can be created with `createAction(...)`)
	// You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
	.addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
		return {
			...state,
			message: action.payload,
		};
	})
	// action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
	.addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
		return new SampleState();
	})
	// creates reducer (implementation delegates to `handleActions(previouslyAddedReducersAsMap)` in `redux-actions` package)
	.toReducer();

export default reducer;

As You can see, You don't need to specify types of reducer function state and action parameters or reducer function return type - State types for reducer functions (reducer function state argument and return type) are inferred based on initial state (new ReducerFactory(initialState)). Type of action (and action.payload) are inferred based on either

a) first argument type when passing in action creator as first argument (using ReducerFactory.addReducer(actionCreator: ActionFunctions, reducerFunction))

b) (optional) generic type of addReducer that defaults to void (using ReducerFactory.addReducer<ActionPayloadType>(actionType: string, reducerFunction))

So You don't need to specify any TypeScript type annotations for any parameters or return types. This even works with (noImplicitAny TypeScript compiler option) - how cool is that?

But if You want to add reducer function by action type (instead of redux-actions action creator), and You want to use action.payload, then it isn't possible for TypeScript compiler to figure out what the payload type should be, so You must provide its type via generic type parameter of addReducer<ActionPayloadType>(...).

Type-safety to the limits

Note, that while TypeScript compiler prevents You from returning less properties than present on state type, it allows You to return more properties than present on state type if state type is inferred. That would be highly unlikely a problem if You don't use object spread syntax on state object to create new state:

		return {
			message: state.message,
			count: state.count + action.payload,
			thisPropertyDoesNotExist: 'Oops! this problem is detected by TypeScript compiler if return type is explicitly set on the reducer arrow function',
		};

(as you probably wouldn't mistype the property that didn't exist on state type), but it could become a problem if You do use object spread syntax on state object to create new state:

		return {
			...state,
			typoInPropertyName: state.count + action.payload, // unintended assignment to wrong property
			thisPropertyDoesNotExist: 'Oops! this problem is detected by TypeScript compiler if return type is explicitly set on the reducer arrow function',
		};

as You may mistype property name You want to assign, resulting in incorrect code without TypeScript error.

So if You want to be super-safe and catch this kind of errors with TypeScript compiler, You should explicitly add return type for added reducer functions:

     // .addReducer(add, (state, action) => { // wouldn't detect following problem
	.addReducer(add, (state, action): SampleState => { // would detect following problem
		return {
			...state,
			count: state.count + action.payload,
			thisPropertyDoesNotExist: 'Oops! this problem is detected by TypeScript compiler if return type is explicitly set on the reducer arrow function',
			// ^^^ Error: TS2322: Type ... is not assignable to type 'SampleState'.
			// Object literal may only specify known properties, and 'thisPropertyDoesNotExist' does not exist in type 'SampleState'.
		};
	})

It is up to You to decide if You want to add return type explicitly to gain maximum type safety or leave it out (and perhaps catch these potential issues with automated or manual tests) and still get fairly good type safety.

Code style

You don't need to define class for state type, even tough this is my personal perference:

class SampleState {
	count = 0;
	message: string = null;
}
const reducer = new ReducerFactory(new SampleState())

Alternatively You could write:

const initialState = {
	count: 0,
	message: null as string,
};
const sampleReducer = new ReducerFactory(initialState)

But it is a matter of code style and totally up to You.

More information

  • tests cover all use-cases
  • API documentation (generated TypeScript type definitions file based on source code and comments)