action-typed
TypeScript icon, indicating that this package has built-in type declarations

2.0.0 • Public • Published

Action-typed

Better type-safety, with less actual typing for Redux actions

Install

npm i action-typed
# or 
yarn add action-typed

Why

Video walkthrough if you prefer: https://www.youtube.com/watch?v=v263zMyVv6k

  • Maximum type safety from minimal code 👀
  • No need to preload redux with all possible types or use an augmented store from another library - soundness is checked at the point of interaction.
  • All types are derived from the implementation 🧙‍♀️
  • No 'boilerplate', just write a simple JavaScript object and provide provide types for your expected arguments
  • 100% interop with existing Redux middlewares (eg connected routers)
  • Exposes a helper type to convert your raw JavaScript object into a tagged union (discriminated union/algebraic data type)
  • Accurate type narrowing and safety when needed (eg: in reducers)
  • No need to dream up names for action creators, instead just use the type itself to distinguish between actions
  • No need to wrap payloads in {type, payload}s, it feels more like working with type constructors
  • Result/return types of all action creators is inferred from the implementation
  • No need to write separate types - they are all generated at run time and are 100% safe
  • Zero-cost library, adds nothing to your bundle size
  • Action names can be strings/Ennis/consts
  • Namespace your actions however you like (anything that's a valid object key)
  • Get type safety in action creators, components, reducers, thunks, epics or anywhere else - all derived from the same JS object

Example

user.actions.ts

import {ActionHandler, msgCreator} from "action-typed";
 
// this replaces any action-creators you may have 😍
const messages = {
    SignedIn: (firstname: string, lastname: string) => ({firstname, lastname}),
    Token: (token: string) => token,
    SignOut: () => undefined,
};
 
export const Msg = msgCreator(messages);
export type Handler = ActionHandler<typeof messages>

index.ts

import {combineReducers, createStore} from "redux";
import {userReducer} from "./user.reducer";
import {Msg} from "./user.actions";
 
const root = combineReducers({
    user: userReducer
});
 
const store = createStore(root);
 
store.dispatch(
    // you can't make a mistake here - the string "SignedIn" is type-safe, and it
    // dictates what the remaining parameters should be 👌
    Msg("SignedIn", "shane", "osbourne")
);

user.reducer.ts

import {Handler} from "./user.actions";
 
type State = {
    token: string
};
 
const initialState: State = { token: "" };
 
//
// this uses the helper union type that's inferred from the JS object
//                                                           ↓
export function userReducer(state = initialState, action: Handler): State { 
    switch (action.type) {
        // matching "Token" here narrows the type of `action`
        // that means you get full type-safety on the value of 'payload' 👌
        case "Token": {
            return { ...state, token: action.payload }
        }
    }
    return state;
}

component example mapDispatchToProps

import React, { Component } from 'react';
import {connect} from "react-redux";
import {Msg} from "./actions/counter.actions";
import {StoreState} from "./configureStore";
 
type AppProps = {
  Msg: typeof Msg,
  counter: number,
}
 
class App extends Component<AppProps> {
  render() {
    const {Msg, counter} = this.props;
    return (
      <div className="App">
          {counter}
          <button onClick={() => Msg("Increment")}>Increment</button>
          <button onClick={() => Msg("Decrement", 20)}>Decrement by 20</button>
      </div>
    );
  }
}
 
export default connect(
    (x: StoreState) => ({ counter: x.counter }),
    // don't map all your separate action-creators here
    {Msg}
)(App);

Package Sidebar

Install

npm i action-typed

Weekly Downloads

205

Version

2.0.0

License

ISC

Unpacked Size

8.14 kB

Total Files

5

Last publish

Collaborators

  • shakyshane