Coder Social home page Coder Social logo

action-typed's Introduction

Action-typed

Better type-sa<fety, with less actual typing for Redux a>ctions

Install

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

V2 (for version 1.x, see below)

Define your event types

import { ActionMap, createMsg } from 'action-typed';

export enum Storage {
    Set = 'Storage/Set',
    Delete = 'Storage/Delete',
}

export type Messages = {
    [Storage.Set]: { key: string; value: any; };
    [Storage.Delete]: string;
};

export const Msg = createMsg<Messages>();
export type TypeMap = ActionMap<Messages>;
export type Actions = TypeMap[keyof TypeMap];

Now use Msg to create type-safe events:

Msg(Storage.Set, {key: 'user', value: {name: "shane"}})

V1

NOTE the following applies to V1 - we recommend switching to V2 above.

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/enums/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);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.