Giter VIP home page Giter VIP logo

piotrwitek / typesafe-actions Goto Github PK

View Code? Open in Web Editor NEW
2.4K 15.0 97.0 1.67 MB

Typesafe utilities for "action-creators" in Redux / Flux Architecture

Home Page: https://codesandbox.io/s/github/piotrwitek/typesafe-actions/tree/master/codesandbox

License: MIT License

TypeScript 95.77% JavaScript 3.36% HTML 0.67% CSS 0.20%
redux-actions typescript static-typing redux action-creator javascript

typesafe-actions's Introduction

typesafe-actions

Typesafe utilities designed to reduce types verbosity and complexity in Redux Architecture.

This library is part of the React & Redux TypeScript Guide ecosystem ๐Ÿ“–

Latest Stable Version NPM Downloads NPM Downloads Bundlephobia Size

Build Status Dependency Status License Join the community on Spectrum

Found it useful? Want more updates?

Show your support by giving a โญ

Buy Me a Coffee Become a Patron



What's new?

๐ŸŽ‰ Now updated to support TypeScript v3.7 ๐ŸŽ‰

โš ๏ธ Library was recently updated to v5 โš ๏ธ
Current API Docs and Tutorial are outdated (from v4), so temporarily please use this issue as v5.x.x API Docs.



Features

Examples

Goals

  • Secure and Minimal - no third-party dependencies, according to size-snapshot (Minified: 3.48 KB, Gzipped: 1.03 KB), check also on bundlephobia
  • Optimized - distribution packages bundled in 3 different formats (cjs, esm and umd) with separate bundles for dev & prod (same as react)
  • Quality - complete test-suite for an entire API surface containing regular runtime tests and extra type-tests to guarantee type soundness and to prevent regressions in the future TypeScript versions
  • Performance - integrated performance benchmarks to guarantee that the computational complexity of types are in check and there are no slow-downs when your application grow npm run benchmark:XXX

Table of Contents


Installation

# NPM
npm install typesafe-actions

# YARN
yarn add typesafe-actions

โ‡ง back to top


Tutorial v4 (v5 is WIP #188)

To showcase the flexibility and the power of the type-safety provided by this library, let's build the most common parts of a typical todo-app using a Redux architecture:

WARNING
Please make sure that you are familiar with the following concepts of programming languages to be able to follow along: Type Inference, Control flow analysis, Tagged union types, Generics and Advanced Types.

โ‡ง back to top

Constants

RECOMMENDATION:
When using typesafe-actions in your project you won't need to export and reuse string constants. It's because action-creators created by this library have static property with action type that you can easily access using actions-helpers and then use it in reducers, epics, sagas, and basically any other place. This will simplify your codebase and remove some boilerplate code associated with the usage of string constants. Check our /codesandbox application to learn some best-practices to create such codebase.

Limitations of TypeScript when working with string constants - when using string constants as action type property, please make sure to use simple string literal assignment with const. This limitation is coming from the type-system, because all the dynamic string operations (e.g. string concatenation, template strings and also object used as a map) will widen the literal type to its super-type, string. As a result this will break contextual typing for action object in reducer cases.

// Example file: './constants.ts'

// WARNING: Incorrect usage
export const ADD = prefix + 'ADD'; // => string
export const ADD = `${prefix}/ADD`; // => string
export default {
   ADD: '@prefix/ADD', // => string
}

// Correct usage
export const ADD = '@prefix/ADD'; // => '@prefix/ADD'
export const TOGGLE = '@prefix/TOGGLE'; // => '@prefix/TOGGLE'
export default ({
  ADD: '@prefix/ADD', // => '@prefix/ADD'
} as const) // working in TS v3.4 and above => https://github.com/Microsoft/TypeScript/pull/29510

โ‡ง back to top

Actions

Different projects have different needs, and conventions vary across teams, and this is why typesafe-actions was designed with flexibility in mind. It provides three different major styles so you can choose whichever would be the best fit for your team.

1. Basic actions

action and createAction are creators that can create actions with predefined properties ({ type, payload, meta }). This makes them concise but also opinionated.

Important property is that resulting action-creator will have a variadic number of arguments and preserve their semantic names (id, title, amount, etc...).

These two creators are very similar and the only real difference is that action WILL NOT WORK with action-helpers.

import { action, createAction } from 'typesafe-actions';

export const add = (title: string) => action('todos/ADD', { id: cuid(), title, completed: false });
// add: (title: string) => { type: "todos/ADD"; payload: { id: string, title: string, completed: boolean; }; }

export const add = createAction('todos/ADD', action => {
  // Note: "action" callback does not need "type" parameter
  return (title: string) => action({ id: cuid(), title, completed: false });
});
// add: (title: string) => { type: "todos/ADD"; payload: { id: string, title: string, completed: boolean; }; }

2. FSA compliant actions

This style is aligned with Flux Standard Action, so your action object shape is constrained to ({ type, payload, meta, error }). It is using generic type arguments for meta and payload to simplify creation of type-safe action-creators.

It is important to notice that in the resulting action-creator arguments are also constrained to the predefined: (payload, meta), making it the most opinionated creator.

TIP: This creator is the most compatible with redux-actions in case you are migrating.

import { createStandardAction } from 'typesafe-actions';

export const toggle = createStandardAction('todos/TOGGLE')<string>();
// toggle: (payload: string) => { type: "todos/TOGGLE"; payload: string; }

export const add = createStandardAction('todos/ADD').map(
  (title: string) => ({
    payload: { id: cuid(), title, completed: false },
  })
);
// add: (payload: string) => { type: "todos/ADD"; payload: { id: string, title: string, completed: boolean; }; }

3. Custom actions (non-standard use-cases)

This approach will give us the most flexibility of all creators, providing a variadic number of named parameters and custom properties on action object to fit all the custom use-cases.

import { createCustomAction } from 'typesafe-actions';

const add = createCustomAction('todos/ADD', type => {
  return (title: string) => ({ type, id: cuid(), title, completed: false });
});
// add: (title: string) => { type: "todos/ADD"; id: string; title: string; completed: boolean; }

TIP: For more examples please check the API Docs.

RECOMMENDATION
Common approach is to create a RootAction in the central point of your redux store - it will represent all possible action types in your application. You can even merge it with third-party action types as shown below to make your model complete.

// types.d.ts
// example of including `react-router` actions in `RootAction`
import { RouterAction, LocationChangeAction } from 'react-router-redux';
import { TodosAction } from '../features/todos';

type ReactRouterAction = RouterAction | LocationChangeAction;

export type RootAction =
  | ReactRouterAction
  | TodosAction;

โ‡ง back to top

Action Helpers

Now I want to show you action-helpers and explain their use-cases. We're going to implement a side-effect responsible for showing a success toast when user adds a new todo.

Important thing to notice is that all these helpers are acting as a type-guard so they'll narrow tagged union type (RootAction) to a specific action type that we want.

Using action-creators instances instead of type-constants

Instead of type-constants we can use action-creators instance to match specific actions in reducers and epics cases. It works by adding a static property on action-creator instance which contains the type string.

The most common one is getType, which is useful for regular reducer switch cases:

  switch (action.type) {
    case getType(todos.add):
      // below action type is narrowed to: { type: "todos/ADD"; payload: Todo; }
      return [...state, action.payload];
    ...

Then we have the isActionOf helper which accept action-creator as first parameter matching actions with corresponding type passed as second parameter (it's a curried function).

// epics.ts
import { isActionOf } from 'typesafe-actions';

import { add } from './actions';

const addTodoToast: Epic<RootAction, RootAction, RootState, Services> = (action$, state$, { toastService }) =>
  action$.pipe(
    filter(isActionOf(add)),
    tap(action => { // here action type is narrowed to: { type: "todos/ADD"; payload: Todo; }
      toastService.success(...);
    })
    ...
    
  // Works with multiple actions! (with type-safety up to 5)
  action$.pipe(
    filter(isActionOf([add, toggle])) // here action type is narrowed to a smaller union:
    // { type: "todos/ADD"; payload: Todo; } | { type: "todos/TOGGLE"; payload: string; }

Using regular type-constants

Alternatively if your team prefers to use regular type-constants you can still do that.

We have an equivalent helper (isOfType) which accept type-constants as parameter providing the same functionality.

// epics.ts
import { isOfType } from 'typesafe-actions';

import { ADD } from './constants';

const addTodoToast: Epic<RootAction, RootAction, RootState, Services> = (action$, state$, { toastService }) =>
  action$.pipe(
    filter(isOfType(ADD)),
    tap(action => { // here action type is narrowed to: { type: "todos/ADD"; payload: Todo; }
    ...
    
  // Works with multiple actions! (with type-safety up to 5)
  action$.pipe(
    filter(isOfType([ADD, TOGGLE])) // here action type is narrowed to a smaller union:
    // { type: "todos/ADD"; payload: Todo; } | { type: "todos/TOGGLE"; payload: string; }

TIP: you can use action-helpers with other types of conditional statements.

import { isActionOf, isOfType } from 'typesafe-actions';

if (isActionOf(actions.add, action)) {
  // here action is narrowed to: { type: "todos/ADD"; payload: Todo; }
}
// or with type constants
if (isOfType(types.ADD, action)) {
  // here action is narrowed to: { type: "todos/ADD"; payload: Todo; }
}

โ‡ง back to top

Reducers

Extending internal types to enable type-free syntax with createReducer

We can extend internal types of typesafe-actions module with RootAction definition of our application so that you don't need to pass generic type arguments with createReducer API:

// types.d.ts
import { ActionType } from 'typesafe-actions';

export type RootAction = ActionType<typeof import('./actions').default>;

declare module 'typesafe-actions' {
  interface Types {
    RootAction: RootAction;
  }
}

// now you can use
createReducer(...)
// instead of
createReducer<State, Action>(...)

Using createReducer API with type-free syntax

We can prevent a lot of boilerplate code and type errors using this powerful and completely typesafe API.

Using handleAction chain API:

// using action-creators
const counterReducer = createReducer(0)
  // state and action type is automatically inferred and return type is validated to be exact type
  .handleAction(add, (state, action) => state + action.payload)
  .handleAction(add, ... // <= error is shown on duplicated or invalid actions
  .handleAction(increment, (state, _) => state + 1)
  .handleAction(... // <= error is shown when all actions are handled
  
  // or handle multiple actions using array
  .handleAction([add, increment], (state, action) =>
    state + (action.type === 'ADD' ? action.payload : 1)
  );

// all the same scenarios are working when using type-constants
const counterReducer = createReducer(0)
  .handleAction('ADD', (state, action) => state + action.payload)
  .handleAction('INCREMENT', (state, _) => state + 1);
  
counterReducer(0, add(4)); // => 4
counterReducer(0, increment()); // => 1

Alternative usage with regular switch reducer

First we need to start by generating a tagged union type of actions (TodosAction). It's very easy to do by using ActionType type-helper.

import { ActionType } from 'typesafe-actions';

import * as todos from './actions';
export type TodosAction = ActionType<typeof todos>;

Now we define a regular reducer function by annotating state and action arguments with their respective types (TodosAction for action type).

export default (state: Todo[] = [], action: TodosAction) => {

Now in the switch cases we can use the type property of action to narrowing the union type of TodosAction to an action that is corresponding to that type.

  switch (action.type) {
    case getType(add):
      // below action type is narrowed to: { type: "todos/ADD"; payload: Todo; }
      return [...state, action.payload];
    ...

โ‡ง back to top

Async-Flows

With redux-observable epics

To handle an async-flow of http request lets implement an epic. The epic will call a remote API using an injected todosApi client, which will return a Promise that we'll need to handle by using three different actions that correspond to triggering, success and failure.

To help us simplify the creation process of necessary action-creators, we'll use createAsyncAction function providing us with a nice common interface object { request: ... , success: ... , failure: ... } that will nicely fit with the functional API of RxJS. This will mitigate redux verbosity and greatly reduce the maintenance cost of type annotations for actions and action-creators that would otherwise be written explicitly.

// actions.ts
import { createAsyncAction } from 'typesafe-actions';

const fetchTodosAsync = createAsyncAction(
  'FETCH_TODOS_REQUEST',
  'FETCH_TODOS_SUCCESS',
  'FETCH_TODOS_FAILURE',
  'FETCH_TODOS_CANCEL'
)<string, Todo[], Error, string>();

// epics.ts
import { fetchTodosAsync } from './actions';

const fetchTodosFlow: Epic<RootAction, RootAction, RootState, Services> = (action$, state$, { todosApi }) =>
  action$.pipe(
    filter(isActionOf(fetchTodosAsync.request)),
    switchMap(action =>
      from(todosApi.getAll(action.payload)).pipe(
        map(fetchTodosAsync.success),
        catchError((message: string) => of(fetchTodosAsync.failure(message))),
        takeUntil(action$.pipe(filter(isActionOf(fetchTodosAsync.cancel)))),
      )
    )
  );

With redux-saga sagas

With sagas it's not possible to achieve the same degree of type-safety as with epics because of limitations coming from redux-saga API design.

Typescript issues:

Here is the latest recommendation although it's not fully optimal. If you managed to cook something better, please open an issue to share your finding with us.

import { createAsyncAction, createReducer } from 'typesafe-actions';
import { put, call, takeEvery, all } from 'redux-saga/effects';

// Create the set of async actions
const fetchTodosAsync = createAsyncAction(
  'FETCH_TODOS_REQUEST',
  'FETCH_TODOS_SUCCESS',
  'FETCH_TODOS_FAILURE'
)<string, Todo[], Error>();

// Handle request saga
function* addTodoSaga(action: ReturnType<typeof fetchTodosAsync.request>): Generator {
  try {
    const response: Todo[] = yield call(todosApi.getAll, action.payload);

    yield put(fetchTodosAsync.success(response));
  } catch (err) {
    yield put(fetchTodosAsync.failure(err));
  }
}

// Main saga
function* mainSaga() {
    yield all([
        takeEvery(fetchTodosAsync.request, addTodoSaga),
    ]);
}

// Handle success reducer
export const todoReducer = createReducer({})
    .handleAction(fetchTodosAsync.success, (state, action) => ({ ...state, todos: action.payload }));

โ‡ง back to top


API Docs v4 (v5 is WIP #189)

Action-Creators API

action

Simple action factory function to simplify creation of type-safe actions.

WARNING:
This approach will NOT WORK with action-helpers (such as getType and isActionOf) because it is creating action objects while all the other creator functions are returning enhanced action-creators.

action(type, payload?, meta?, error?)

Examples: > Advanced Usage Examples

const increment = () => action('INCREMENT');
// { type: 'INCREMENT'; }

const createUser = (id: number, name: string) =>
  action('CREATE_USER', { id, name });
// { type: 'CREATE_USER'; payload: { id: number; name: string }; }

const getUsers = (params?: string) =>
  action('GET_USERS', undefined, params);
// { type: 'GET_USERS'; meta: string | undefined; }

TIP: Starting from TypeScript v3.4 you can achieve similar results using new as const operator.

const increment = () => ({ type: 'INCREMENT' } as const);

createAction

Create an enhanced action-creator with unlimited number of arguments.

  • Resulting action-creator will preserve semantic names of their arguments (id, title, amount, etc...).
  • Returned action object have predefined properties ({ type, payload, meta })
createAction(type)
createAction(type, actionCallback => {
  return (namedArg1, namedArg2, ...namedArgN) => actionCallback(payload?, meta?)
})

TIP: Injected actionCallback argument is similar to action API but doesn't need the "type" parameter

Examples: > Advanced Usage Examples

import { createAction } from 'typesafe-actions';

// - with type only
const increment = createAction('INCREMENT');
dispatch(increment());
// { type: 'INCREMENT' };

// - with type and payload
const add = createAction('ADD', action => {
  return (amount: number) => action(amount);
});
dispatch(add(10));
// { type: 'ADD', payload: number }

// - with type and meta
const getTodos = createAction('GET_TODOS', action => {
  return (params: Params) => action(undefined, params);
});
dispatch(getTodos('some_meta'));
// { type: 'GET_TODOS', meta: Params }

// - and finally with type, payload and meta
const getTodo = createAction('GET_TODO', action => {
  return (id: string, meta: string) => action(id, meta);
});
dispatch(getTodo('some_id', 'some_meta'));
// { type: 'GET_TODO', payload: string, meta: string }

โ‡ง back to top

createStandardAction

Create an enhanced action-creator compatible with Flux Standard Action to reduce boilerplate and enforce convention.

  • Resulting action-creator have predefined arguments (payload, meta)
  • Returned action object have predefined properties ({ type, payload, meta, error })
  • But it also contains a .map() method that allow to map (payload, meta) arguments to a custom action object ({ customProp1, customProp2, ...customPropN })
createStandardAction(type)()
createStandardAction(type)<TPayload, TMeta?>()
createStandardAction(type).map((payload, meta) => ({ customProp1, customProp2, ...customPropN }))

TIP: Using undefined as generic type parameter you can make the action-creator function require NO parameters.

Examples: > Advanced Usage Examples

import { createStandardAction } from 'typesafe-actions';

// Very concise with use of generic type arguments
// - with type only
const increment = createStandardAction('INCREMENT')();
const increment = createStandardAction('INCREMENT')<undefined>();
increment(); // { type: 'INCREMENT' } (no parameters are required)


// - with type and payload
const add = createStandardAction('ADD')<number>();
add(10); // { type: 'ADD', payload: number }

// - with type and meta
const getData = createStandardAction('GET_DATA')<undefined, string>();
getData(undefined, 'meta'); // { type: 'GET_DATA', meta: string }

// - and finally with type, payload and meta
const getData = createStandardAction('GET_DATA')<number, string>();
getData(1, 'meta'); // { type: 'GET_DATA', payload: number, meta: string }

// Can map payload and meta arguments to a custom action object
const notify = createStandardAction('NOTIFY').map(
  (payload: string, meta: Meta) => ({
    from: meta.username,
    message: `${meta.username}: ${payload}`,
    messageType: meta.type,
    datetime: new Date(),
  })
);

dispatch(notify('Hello!', { username: 'Piotr', type: 'announcement' }));
// { type: 'NOTIFY', from: string, message: string, messageType: MessageType, datetime: Date }

โ‡ง back to top

createCustomAction

Create an enhanced action-creator with unlimited number of arguments and custom properties on action object.

  • Resulting action-creator will preserve semantic names of their arguments (id, title, amount, etc...).
  • Returned action object have custom properties ({ type, customProp1, customProp2, ...customPropN })
createCustomAction(type, type => {
  return (namedArg1, namedArg2, ...namedArgN) => ({ type, customProp1, customProp2, ...customPropN })
})

Examples: > Advanced Usage Examples

import { createCustomAction } from 'typesafe-actions';

const add = createCustomAction('CUSTOM', type => {
  return (first: number, second: number) => ({ type, customProp1: first, customProp2: second });
});

dispatch(add(1));
// { type: "CUSTOM"; customProp1: number; customProp2: number; }

โ‡ง back to top

createAsyncAction

Create an object containing three enhanced action-creators to simplify handling of async flows (e.g. network request - request/success/failure).

createAsyncAction(
  requestType, successType, failureType, cancelType?
)<TRequestPayload, TSuccessPayload, TFailurePayload, TCancelPayload?>()
AsyncActionCreator
type AsyncActionCreator<
  [TRequestType, TRequestPayload],
  [TSuccessType, TSuccessPayload],
  [TFailureType, TFailurePayload],
  [TCancelType, TCancelPayload]?
> = {
  request: StandardActionCreator<TRequestType, TRequestPayload>,
  success: StandardActionCreator<TSuccessType, TSuccessPayload>,
  failure: StandardActionCreator<TFailureType, TFailurePayload>,
  cancel?: StandardActionCreator<TCancelType, TCancelPayload>,
}

TIP: Using undefined as generic type parameter you can make the action-creator function require NO parameters.

Examples: > Advanced Usage Examples

import { createAsyncAction, AsyncActionCreator } from 'typesafe-actions';

const fetchUsersAsync = createAsyncAction(
  'FETCH_USERS_REQUEST',
  'FETCH_USERS_SUCCESS',
  'FETCH_USERS_FAILURE'
)<string, User[], Error>();

dispatch(fetchUsersAsync.request(params));

dispatch(fetchUsersAsync.success(response));

dispatch(fetchUsersAsync.failure(err));

const fn = (
  a: AsyncActionCreator<
    ['FETCH_USERS_REQUEST', string],
    ['FETCH_USERS_SUCCESS', User[]],
    ['FETCH_USERS_FAILURE', Error]
  >
) => a;
fn(fetchUsersAsync);

// There is 4th optional argument to declare cancel action
const fetchUsersAsync = createAsyncAction(
  'FETCH_USERS_REQUEST',
  'FETCH_USERS_SUCCESS',
  'FETCH_USERS_FAILURE'
  'FETCH_USERS_CANCEL'
)<string, User[], Error, string>();

dispatch(fetchUsersAsync.cancel('reason'));

const fn = (
  a: AsyncActionCreator<
    ['FETCH_USERS_REQUEST', string],
    ['FETCH_USERS_SUCCESS', User[]],
    ['FETCH_USERS_FAILURE', Error],
    ['FETCH_USERS_CANCEL', string]
  >
) => a;
fn(fetchUsersAsync);

โ‡ง back to top


Reducer-Creators API

createReducer

Create a typesafe reducer

createReducer<TState, TRootAction>(initialState, handlersMap?)
// or
createReducer<TState, TRootAction>(initialState)
  .handleAction(actionCreator, reducer)
  .handleAction([actionCreator1, actionCreator2, ...actionCreatorN], reducer)
  .handleType(type, reducer)
  .handleType([type1, type2, ...typeN], reducer)

Examples: > Advanced Usage Examples

TIP: You can use reducer API with a type-free syntax by Extending internal types, otherwise you'll have to pass generic type arguments like in below examples

// type-free syntax doesn't require generic type arguments
const counterReducer = createReducer(0, { 
  ADD: (state, action) => state + action.payload,
  [getType(increment)]: (state, _) => state + 1,
})

Object map style:

import { createReducer, getType } from 'typesafe-actions'

type State = number;
type Action = { type: 'ADD', payload: number } | { type: 'INCREMENT' };

const counterReducer = createReducer<State, Action>(0, { 
  ADD: (state, action) => state + action.payload,
  [getType(increment)]: (state, _) => state + 1,
})

Chain API style:

// using action-creators
const counterReducer = createReducer<State, Action>(0)
  .handleAction(add, (state, action) => state + action.payload)
  .handleAction(increment, (state, _) => state + 1)

  // handle multiple actions by using array
  .handleAction([add, increment], (state, action) =>
    state + (action.type === 'ADD' ? action.payload : 1)
  );

// all the same scenarios are working when using type-constants
const counterReducer = createReducer<State, Action>(0)
  .handleType('ADD', (state, action) => state + action.payload)
  .handleType('INCREMENT', (state, _) => state + 1);

Extend or compose reducers - every operation is completely typesafe:

const newCounterReducer = createReducer<State, Action>(0)
  .handleAction('SUBTRACT', (state, action) => state - action.payload)
  .handleAction('DECREMENT', (state, _) => state - 1);

const bigReducer = createReducer<State, Action>(0, {
  ...counterReducer.handlers, // typesafe
  ...newCounterReducer.handlers, // typesafe
  SUBTRACT: decrementReducer.handlers.DECREMENT, // <= error, wrong type
})

โ‡ง back to top


Action-Helpers API

getType

Get the type property value (narrowed to literal type) of given enhanced action-creator.

getType(actionCreator)

> Advanced Usage Examples

Examples:

import { getType, createStandardAction } from 'typesafe-actions';

const add = createStandardAction('ADD')<number>();

// In switch reducer
switch (action.type) {
  case getType(add):
    // action type is { type: "ADD"; payload: number; }
    return state + action.payload;

  default:
    return state;
}

// or with conditional statements
if (action.type === getType(add)) {
  // action type is { type: "ADD"; payload: number; }
}

โ‡ง back to top

isActionOf

Check if action is an instance of given enhanced action-creator(s) (it will narrow action type to a type of given action-creator(s))

WARNING: Regular action creators and action will not work with this helper

// can be used as a binary function
isActionOf(actionCreator, action)
// or as a curried function
isActionOf(actionCreator)(action)
// also accepts an array
isActionOf([actionCreator1, actionCreator2, ...actionCreatorN], action)
// with its curried equivalent
isActionOf([actionCreator1, actionCreator2, ...actionCreatorN])(action)

Examples: > Advanced Usage Examples

import { addTodo, removeTodo } from './todos-actions';

// Works with any filter type function (`Array.prototype.filter`, lodash, ramda, rxjs, etc.)
// - single action
[action1, action2, ...actionN]
  .filter(isActionOf(addTodo)) // only actions with type `ADD` will pass
  .map((action) => {
    // action type is { type: "todos/ADD"; payload: Todo; }
    ...
    
// - multiple actions
[action1, action2, ...actionN]
  .filter(isActionOf([addTodo, removeTodo])) // only actions with type `ADD` or 'REMOVE' will pass
  .do((action) => {
    // action type is { type: "todos/ADD"; payload: Todo; } | { type: "todos/REMOVE"; payload: Todo; }
    ...
      
// With conditional statements
// - single action
if(isActionOf(addTodo, action)) {
  return iAcceptOnlyTodoType(action.payload);
  // action type is { type: "todos/ADD"; payload: Todo; }
}
// - multiple actions
if(isActionOf([addTodo, removeTodo], action)) {
  return iAcceptOnlyTodoType(action.payload);
  // action type is { type: "todos/ADD"; payload: Todo; } | { type: "todos/REMOVE"; payload: Todo; }
}

โ‡ง back to top

isOfType

Check if action type property is equal given type-constant(s) (it will narrow action type to a type of given action-creator(s))

// can be used as a binary function
isOfType(type, action)
// or as curried function
isOfType(type)(action)
// also accepts an array
isOfType([type1, type2, ...typeN], action)
// with its curried equivalent
isOfType([type1, type2, ...typeN])(action)

Examples: > Advanced Usage Examples

import { ADD, REMOVE } from './todos-types';

// Works with any filter type function (`Array.prototype.filter`, lodash, ramda, rxjs, etc.)
// - single action
[action1, action2, ...actionN]
  .filter(isOfType(ADD)) // only actions with type `ADD` will pass
  .map((action) => {
    // action type is { type: "todos/ADD"; payload: Todo; }
    ...
    
// - multiple actions
[action1, action2, ...actionN]
  .filter(isOfType([ADD, REMOVE])) // only actions with type `ADD` or 'REMOVE' will pass
  .do((action) => {
    // action type is { type: "todos/ADD"; payload: Todo; } | { type: "todos/REMOVE"; payload: Todo; }
    ...
      
// With conditional statements
// - single action
if(isOfType(ADD, action)) {
  return iAcceptOnlyTodoType(action.payload);
  // action type is { type: "todos/ADD"; payload: Todo; }
}
// - multiple actions
if(isOfType([ADD, REMOVE], action)) {
  return iAcceptOnlyTodoType(action.payload);
  // action type is { type: "todos/ADD"; payload: Todo; } | { type: "todos/REMOVE"; payload: Todo; }
}

โ‡ง back to top


Type-Helpers API

Below helper functions are very flexible generalizations, works great with nested structures and will cover numerous different use-cases.

ActionType

Powerful type-helper that will infer union type from import * as ... or action-creator map object.

import { ActionType } from 'typesafe-actions';

// with "import * as ..."
import * as todos from './actions';
export type TodosAction = ActionType<typeof todos>;
// TodosAction: { type: 'action1' } | { type: 'action2' } | { type: 'action3' }

// with nested action-creator map case
const actions = {
  action1: createAction('action1'),
  nested: {
    action2: createAction('action2'),
    moreNested: {
      action3: createAction('action3'),
    },
  },
};
export type RootAction = ActionType<typeof actions>;
// RootAction: { type: 'action1' } | { type: 'action2' } | { type: 'action3' }

โ‡ง back to top

StateType

Powerful type helper that will infer state object type from reducer function and nested/combined reducers.

WARNING: working with redux@4+ types

import { combineReducers } from 'redux';
import { StateType } from 'typesafe-actions';

// with reducer function
const todosReducer = (state: Todo[] = [], action: TodosAction) => {
  switch (action.type) {
    case getType(todos.add):
      return [...state, action.payload];
    ...
export type TodosState = StateType<typeof todosReducer>;

// with nested/combined reducers
const rootReducer = combineReducers({
  router: routerReducer,
  counters: countersReducer,
});
export type RootState = StateType<typeof rootReducer>;

โ‡ง back to top


Migration Guides

v4.x.x to v5.x.x

Breaking changes:

  1. In v5 all the deprecated v4 creator functions are available under deprecated named import to help with incremental migration.
// before
import { createAction, createStandardAction, createCustomAction } from "typesafe-actions"

// after
import { deprecated } from "typesafe-actions"
const { createAction, createStandardAction, createCustomAction } = deprecated;
  1. createStandardAction was renamed to createAction and .map method was removed in favor of simpler redux-actions style API.
// before
const withMappedPayloadAndMeta = createStandardAction(
  'CREATE_STANDARD_ACTION'
).map(({ username, message }: Notification) => ({
  payload: `${username}: ${message}`,
  meta: { username, message },
}));

// after
const withMappedPayloadAndMeta = createAction(
  'CREATE_STANDARD_ACTION',
  ({ username, message }: Notification) => `${username}: ${message}`, // payload creator
  ({ username, message }: Notification) => ({ username, message }) // meta creator
)();
  1. v4 version of createAction was removed. I suggest to refactor to use a new createAction as in point 2, which was simplified and extended to support redux-actions style API.
// before
const withPayloadAndMeta = createAction('CREATE_ACTION', resolve => {
  return (id: number, token: string) => resolve(id, token);
});

// after
const withPayloadAndMeta = createAction(
  'CREATE_ACTION',
  (id: number, token: string) => id, // payload creator
  (id: number, token: string) => token // meta creator
})();
  1. createCustomAction - API was greatly simplified, now it's used like this:
// before
const add = createCustomAction('CUSTOM', type => {
  return (first: number, second: number) => ({ type, customProp1: first, customProp2: second });
});

// after
const add = createCustomAction(
  'CUSTOM',
  (first: number, second: number) => ({ customProp1: first, customProp2: second })
);
  1. AsyncActionCreator should be just renamed to AsyncActionCreatorBuilder.
// before
import { AsyncActionCreator } from "typesafe-actions"

//after
import { AsyncActionCreatorBuilder } from "typesafe-actions"

v3.x.x to v4.x.x

No breaking changes!

v2.x.x to v3.x.x

Minimal supported TypeScript v3.1+.

v1.x.x to v2.x.x

Breaking changes:

  1. createAction
  • In v2 we provide a createActionDeprecated function compatible with v1 createAction to help with incremental migration.
// in v1 we created action-creator like this:
const getTodo = createAction('GET_TODO',
  (id: string, meta: string) => ({
    type: 'GET_TODO',
    payload: id,
    meta: meta,
  })
);

getTodo('some_id', 'some_meta'); // { type: 'GET_TODO', payload: 'some_id', meta: 'some_meta' }

// in v2 we offer few different options - please choose your preference
const getTodoNoHelpers = (id: string, meta: string) => action('GET_TODO', id, meta);

const getTodoWithHelpers = createAction('GET_TODO', action => {
  return (id: string, meta: string) => action(id, meta);
});

const getTodoFSA = createStandardAction('GET_TODO')<string, string>();

const getTodoCustom = createStandardAction('GET_TODO').map(
  ({ id, meta }: { id: string; meta: string; }) => ({
    payload: id,
    meta,
  })
);

โ‡ง back to top

Migrating from redux-actions to typesafe-actions

  • createAction(s)
createAction(type, payloadCreator, metaCreator) => createStandardAction(type)() || createStandardAction(type).map(payloadMetaCreator)

createActions() => // COMING SOON!
  • handleAction(s)
handleAction(type, reducer, initialState) => createReducer(initialState).handleAction(type, reducer)

handleActions(reducerMap, initialState) => createReducer(initialState, reducerMap)

TIP: If migrating from JS -> TS, you can swap out action-creators from redux-actions with action-creators from typesafe-actions in your handleActions handlers. This works because the action-creators from typesafe-actions provide the same toString method implementation used by redux-actions to match actions to the correct reducer.

  • combineActions

Not needed because each function in the API accept single value or array of values for action types or action creators.

โ‡ง back to top


Compatibility Notes

TypeScript support

  • 5.X.X - TypeScript v3.2+
  • 4.X.X - TypeScript v3.2+
  • 3.X.X - TypeScript v3.2+
  • 2.X.X - TypeScript v2.9+
  • 1.X.X - TypeScript v2.7+

Browser support

It's compatible with all modern browsers.

For older browsers support (e.g. IE <= 11) and some mobile devices you need to provide the following polyfills:

Recommended polyfill for IE

To provide the best compatibility please include a popular polyfill package in your application, such as core-js or react-app-polyfill for create-react-app. Please check the React guidelines on how to do that: LINK A polyfill fo IE11 is included in our /codesandbox application.

โ‡ง back to top


Recipes

Restrict Meta type in action creator

Using this recipe you can create an action creator with restricted Meta type with exact object shape.

export type MetaType = {
  analytics?: {
    eventName: string;
  };
};

export const actionWithRestrictedMeta = <T extends string, P>(
  type: T,
  payload: P,
  meta: MetaType
) => action(type, payload, meta);

export const validAction = (payload: string) =>
  actionWithRestrictedMeta('type', payload, { analytics: { eventName: 'success' } }); // OK!

export const invalidAction = (payload: string) =>
  actionWithRestrictedMeta('type', payload, { analytics: { excessProp: 'no way!' } }); // Error
// Object literal may only specify known properties, and 'excessProp' does not exist in type '{ eventName: string; }

โ‡ง back to top


Compare to others

Here you can find out a detailed comparison of typesafe-actions to other solutions.

redux-actions

Lets compare the 3 most common variants of action-creators (with type only, with payload and with payload + meta)

Note: tested with "@types/redux-actions": "2.2.3"

- with type only (no payload)

redux-actions
const notify1 = createAction('NOTIFY');
// resulting type:
// () => {
//   type: string;
//   payload: void | undefined;
//   error: boolean | undefined;
// }

with redux-actions you can notice the redundant nullable payload property and literal type of type property is lost (discrimination of union type would not be possible)

typesafe-actions
const notify1 = () => action('NOTIFY');
// resulting type:
// () => {
//   type: "NOTIFY";
// }

with typesafe-actions there is no excess nullable types and no excess properties and the action "type" property is containing a literal type

- with payload

redux-actions
const notify2 = createAction('NOTIFY',
  (username: string, message?: string) => ({
    message: `${username}: ${message || 'Empty!'}`,
  })
);
// resulting type:
// (t1: string) => {
//   type: string;
//   payload: { message: string; } | undefined;
//   error: boolean | undefined;
// }

first the optional message parameter is lost, username semantic argument name is changed to some generic t1, type property is widened once again and payload is nullable because of broken inference

typesafe-actions
const notify2 = (username: string, message?: string) => action(
  'NOTIFY',
  { message: `${username}: ${message || 'Empty!'}` },
);
// resulting type:
// (username: string, message?: string | undefined) => {
//   type: "NOTIFY";
//   payload: { message: string; };
// }

typesafe-actions infer very precise resulting type, notice working optional parameters and semantic argument names are preserved which is really important for great intellisense experience

- with payload and meta

redux-actions
const notify3 = createAction('NOTIFY',
  (username: string, message?: string) => (
    { message: `${username}: ${message || 'Empty!'}` }
  ),
  (username: string, message?: string) => (
    { username, message }
  )
);
// resulting type:
// (...args: any[]) => {
//   type: string;
//   payload: { message: string; } | undefined;
//   meta: { username: string; message: string | undefined; };
//   error: boolean | undefined;
// }

this time we got a completely broken arguments arity with no type-safety because of any type with all the earlier issues

typesafe-actions
/**
 * typesafe-actions
 */
const notify3 = (username: string, message?: string) => action(
  'NOTIFY',
  { message: `${username}: ${message || 'Empty!'}` },
  { username, message },
);
// resulting type:
// (username: string, message?: string | undefined) => {
//   type: "NOTIFY";
//   payload: { message: string; };
//   meta: { username: string; message: string | undefined; };
// }

typesafe-actions never fail to any type, even with this advanced scenario all types are correct and provide complete type-safety and excellent developer experience

โ‡ง back to top


Motivation

When I started to combine Redux with TypeScript, I was trying to use redux-actions to reduce the maintainability cost and boilerplate of action-creators. Unfortunately, the results were intimidating: incorrect type signatures and broken type-inference cascading throughout the entire code-base (click here for a detailed comparison).

Existing solutions in the wild have been either too verbose because of redundant type annotations (hard to maintain) or used classes (hinders readability and requires using the new keyword ๐Ÿ˜ฑ)

So I created typesafe-actions to address all of the above pain points.

The core idea was to design an API that would mostly use the power of TypeScript type-inference ๐Ÿ’ช to lift the "maintainability burden" of type annotations. In addition, I wanted to make it "look and feel" as close as possible to the idiomatic JavaScript โค๏ธ , so we don't have to write the redundant type annotations that will create additional noise in your code.

โ‡ง back to top


Contributing

You can help make this project better by contributing. If you're planning to contribute please make sure to check our contributing guide: CONTRIBUTING.md

โ‡ง back to top


Funding Issues

You can also help by funding issues. Issues like bug fixes or feature requests can be very quickly resolved when funded through the IssueHunt platform.

I highly recommend to add a bounty to the issue that you're waiting for to increase priority and attract contributors willing to work on it.

Let's fund issues in this repository

โ‡ง back to top


License

MIT License

Copyright (c) 2017 Piotr Witek [email protected] (http://piotrwitek.github.io)

typesafe-actions's People

Contributors

11bit avatar bassettsj avatar carl-foster avatar cha147 avatar chmac avatar chrisabrams avatar idoo avatar jackjackng avatar jaulz avatar jperelli avatar maadhattah avatar matttennison avatar mberneti avatar merturl avatar paszkowskidamian avatar piotrwitek avatar pybuche avatar rovanion avatar runeh avatar sjsyrek avatar totallybiased avatar vadimkorobka avatar vincentjames501 avatar vladst90 avatar yanick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

typesafe-actions's Issues

Type assertion on the whole action

Hi,

I just found out about your library, and I think it is what I have been looking for to substantially reduce boilerplate code for actions and action creators; thank you!

Creating actions (using createAction) and switching on them (using getType) in the reducer works fine, but I was wondering if there's a way to do type checking for a method that expects a specific kind of action.

Example of my actions (I'm using lodash for values and map):

export enum ActionTypes {
  GetCameras = 'GET_CAMERAS',
  GetCamerasSuccss = 'GET_CAMERAS_SUCCESS',
}

export const cameraActions = {
  getCameras: createAction(ActionType.GetCameras, ( /*...*/ ) => ({
    type: ActionTypes.GetCameras,
    /* payload: ... */
  }),
  getCamerasSuccess: createActionTypes.GetCamerasSuccess, (/*...*/) => ({
    type: ActionTypes.GetCameras,
    /* payload: ... */
  }),
};

const returnsOfActions = _.map(_.values(actions), $call));
export type CameraAction = typeof returnsOfActions[number];

Maybe a good example would be a case where the reducer wants to call another method for a specific action type, for example, in my reducer:

export const reducer = (state = initialState, action: CameraAction) => {
  case getType(cameraActions.getCameras):
    return state; /* ... */
  case getType(cameraActions.getCamerasSuccess):
    return receiveCameras(state, action);
  default:
    return state;
};

Now, in my receiveCameras method, I want to make sure the second argument is of type cameraActions.getCamerasSuccess.

I can do something like this:

const receiveCameras = (state: CameraState, action: CameraAction) => {
  if (action.type !== getType(cameraActions.getCamerasSuccess)) {
    return;
  }
  
  // Now my action type is correct
}

I was wondering if I can specify the type when defining the parameter in the method to avoid doing that if check

Thank you

Generic Epic that takes in an AsyncAction?

Hi Piotrwitek,

I'm having some trouble wrapping my head around how to pass an AsyncAction created via CreateAsyncAction while keeping the typing information, for example in my case I am trying to create a generic GraphQL query epic using ApolloClient since all of those will have the same REQUEST/SUCCESS?FAILURE flow:

What type should the asyncAction be so that the isActionOf and the action.Payload resolve properly?

const createAsyncEpic = (asyncAction: <type?>, mutation: any) {
  const epic: Epic<RootAction, RootState> = (action$, state$) =>
      action$.pipe(
        filter(isActionOf(actions.request)),
        withLatestFrom(state$),
        mergeMap(([action, state]) =>
          client
            .mutate({ mutation, variables: { ...action.payload } })
            .then((result: ApolloQueryResult<P2>) => {
              return actions.success(result.data);
            })
            .catch((error: ApolloError) => {
              return actions.failure(error);
            })
        )
      );

    return epic;
};

Thanks!

Creating actions causing some problems

I have 2 use cases

  1. create action without payload, with meta
    const someAction = createAction('something', resolve => () => resolve(undefined, 'some meta info'))
    which gives me { type: 'something' }, but i expect {type: 'something', meta: string }
  2. create action with payload and meta
const someOtherAction = createStandardAction('somethingElse').map((payload?: number) => ({
  meta: 'some meta info',
  payload: payload!,
}))

in this case I have to mark payload parameter as optional and after tell that it's actualy not, using ! operator to get correct action type ({type: 'somethingElse', payload: number, meta: string})

actualy example from docs

export const add = createStandardAction(ADD).map(
  ({ title }: { title: string }) => ({
    payload: { title, id: cuid(), completed: false } as Todo,
  })
);

doesn't work in my project (I use typescript v2.8.3, redux v4, typesafe-actions v2.0.3) with this error message:
[ts]
Argument of type '({ title }: { title: string; }) => { payload: { title: string; id: any; completed: boolean; }; }' is not assignable to parameter of type '(payload?: { title: string; } | undefined, meta?: void | undefined) => { payload: { title: string...'.
Types of parameters '__0' and 'payload' are incompatible.
Type '{ title: string; } | undefined' is not assignable to type '{ title: string; }'.
Type 'undefined' is not assignable to type '{ title: string; }'.

because payload param is optional

New API - v2.0

Road to v2.0: New API

I'm working on New API using the new language feature (conditional types) coming to TypesScript in the next release (v2.8)

I have already published working version that is compatible with recent typescript@rc release

> to install yarn add typesafe-actions@next

Some changes are inspired by the suggestion from the past and I would really appreciate some feedback from their authors:

The implementation of a new API is taking place on the next branch, and will be released on the release of TypeScript v2.8.

all of the examples are working and have test's against them so they should be rock solid

Finished

ActionsUnion

conditional mapped type, will provide much cleaner and experience to get Union Type of actions

import { ActionsUnion } from 'typesafe-actions';
import * as actions from './todos-duck-module';

type RootAction = ActionsUnion<typeof actions>;
  • ActionsUnion works nicely with nested actions from new async action creator (check below)
const actions = {
    async: buildAction('GET_USER').async<number, { name: string }, string>(),
};
type RootAction = ActionsUnion<typeof actions>;
// { type: "GET_USER" & "REQUEST"; payload: number; } | { type: "GET_USER" & "SUCCESS"; payload: { name: string; }; } | { type: "GET_USER" & "FAILURE"; payload: string; }
  • It also works recursively for deeply nested actions
const actions2 = {
      very: { deep: { empty: buildAction('INCREMENT').empty() } },
      payload: buildAction('ADD').payload<number>(),
};
type RootAction = ActionsUnion<typeof actions2>;
// { type: "INCREMENT"; } | { type: "ADD"; payload: number; }

ReturnType

getting type declaration of action will now be possible to derive from action creator on call site without the need to declare tons of boilerplate before-hand:

import { ReturnType, createAction } from 'typesafe-actions';

const increment = createAction('INCREMENT');

function incrementReducer(state: State, action: ReturnType<typeof increment>) {
...

buildAction

import { buildAction } from 'typesafe-actions'

function will accept action type and return an object with multiple methods to create different action creators described below:

  • buildAction('INCREMENT').empty()
const increment = buildAction('INCREMENT').empty();
expect(increment()).toEqual({ type: 'INCREMENT' }); //  { type: 'INCREMENT' }
expect(getType(increment)).toBe('INCREMENT'); // 'INCREMENT'
  • buildAction('INCREMENT').payload<P>()
const add = buildAction('ADD').payload<number>();
expect(add(10)).toEqual({ type: 'ADD', payload: 10 }); // { type: 'ADD'; payload: number }
expect(getType(add)).toBe('ADD'); // 'ADD'
  • buildAction('INCREMENT').async<R, S, F>()
type User = { name: string };
const fetchUsers = buildAction('LIST_USERS').async<void, User[], string>();
expect(fetchUsers.request())
    .toEqual({ type: 'LIST_USERS_REQUEST' }); // { type: 'LIST_USERS' & 'REQUEST' }
expect(fetchUsers.success([{ name: 'Piotr' }]))
    .toEqual({ type: 'LIST_USERS_SUCCESS', payload: [{ name: 'Piotr' }] }); // { type: 'LIST_USERS' & 'SUCCESS'; payload: User[] }
expect(fetchUsers.failure('error message'))
    .toEqual({ type: 'LIST_USERS_FAILURE', payload: 'error message' }); // { type: 'LIST_USERS' & 'FAILURE'; payload: string }
expect(getType(fetchUsers.request)).toBe('LIST_USERS_REQUEST'); // 'LIST_USERS' & 'REQUEST'
expect(getType(fetchUsers.success)).toBe('LIST_USERS_SUCCESS'); // 'LIST_USERS' & 'SUCCESS'
expect(getType(fetchUsers.failure)).toBe('LIST_USERS_FAILURE'); // 'LIST_USERS' & 'FAILURE'
  • buildAction('NOTIFY').fsa<P>(payloadCreator, metaCreator)

API compatible with redux-actions

const notify = buildAction('NOTIFY').fsa<{ username: string; message?: string }>(
    ({ username, message }) => `${username}: ${message || ''}`,
    ({ username, message }) => ({ username, message })
); // { type: 'NOTIFY'; payload: string; meta: { username: string; message?: string }; }
...

Breaking changes

๐ŸŽ‰NO BREAKING CHANGES ๐ŸŽ‰

Known Issues

Migration to 1.2.0

Here is an migration example of example in the docs:

  • simple payload action
// current v1.0
const add = createAction('ADD',
  (amount: number) => ({ type: 'ADD', payload: amount }),
);

// migration to v1.2 (complete type-safety is still preserved)
const add = buildAction('ADD').payload<number>;
  • advanced Flux Standard Action
// current v1.0
const notify = createAction('NOTIFY',
  (username: string, message?: string) => ({
    type: 'NOTIFY',
    payload: `${username}: ${message || ''}`,
    meta: { username, message },
  }),
);
// migration to v1.2 (complete type-safety is still preserved)
const notify = buildAction('NOTIFY').fsa<{ username: string; message?: string }>(
    ({ username, message }) => `${username}: ${message || ''}`,
    ({ username, message }) => ({ username, message })
);

Did you consider pushing this further towards more boilerplate-saving?

I was playing a bit with providing additional createAction methods which have a very specific shape and came up with the following for e.g. actions with a payload:

const createWithPayload = <P, T extends string>(typeString: T): ((payload: P) => PayloadAction<T, P> & TypeGetter<T>) => {
  const f = createAction(typeString, (payload: P) => ({ type: typeString, payload }));
  return f;
};

which you could call like this:

const a = createWithPayload<string, "CHANGE_COMMENT">("CHANGE_COMMENT");

You can push this even further:

const createWithPayload2 = <T extends string>(
  typeString: T
): (<P>() => ((payload: P) => PayloadAction<T, P> & TypeGetter<T>)) => {
  return <P>() => {
    const f = createAction(typeString, (payload: P) => ({ type: typeString, payload }));
    return f;
  };
};

And you call it like this:

const a = createWithPayload2("CHANGE_COMMENT")<string>();

// instead of:

const a = createAction("CHANGE_COMMENT", (payload: string) => ({
  type: "CHANGE_COMMENT",
  payload
}));

What do you think?

buildAction with a union type payload

I'm currently using v1.2.0-rc.1 to take advantage of the new TypeScript features. However when I use buildAction to make an action creator with a payload that is a union type I'm running into issues. Here's what my build action looks like:

action: buildAction('ACTION').payload<string | null>(),

When I try to build it gives me a type error:

Error:(117, 14) TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((payload: null) => PayloadAction<"ACTION", null>) | ((payload: string) => PayloadAction<"AC...' has no compatible call signatures.

The same thing happens with booleans, since they are a union of true and false:

Error:(44, 12) TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((payload: true) => PayloadAction<"ACTION", true>) | ((payload: false) => Payloa...' has no compatible call signatures.

This issue on the TypeScript repo might be helpful: microsoft/TypeScript#4612

Unable to use an object for defining types with createAction

I was wondering if this is the intended use.

export const actions = {
 login: createAction(types.LOGIN_REQUEST, (email: string, password: string) => ({
    type: types.LOGIN_REQUEST,
    payload: { email, password },
  })),
  loginSuccess: createAction('AUTH/LOGIN_SUCCESS', (authData: any) => ({
    type: 'AUTH/LOGIN_SUCCESS',
    payload: { authData },
  })),
...

With the login action above, I get this output with returnsOfActions:

const returnsOfActions = Object.values(actions).map($call);
type AppAction = typeof returnsOfActions[number];
/**** Here's what the types looks like (hovering my mouse over returnsOfActions **/
const returnsOfActions: ({
    type: string;
    payload: {
        email: string;
        password: string;
    };
} | {
    type: "AUTH/LOGIN_SUCCESS";
    payload: {
        authData: any;
    };

The type for the login_request action returns as string, and not the actual value of types.LOGIN_REQUEST. Is there any way I can continue using my types object, or do I need to use the actual value when I use createAction?

Thanks

Surprising type outcome (i.e. compile error)

Hi,
in the context of creating custom action creators on top of yours, I have come across this compile-time error which identifies an action to not have a payload property even though the runtime (and my brain compiler) says yes. I have reproed the context over here - https://github.com/flq/ts-react-playground/blob/df7f931be2e052f767e65c2639b1873e201a6708/src/repro/actionsAndReducer.ts#L38 . Not super small but it kind of condenses the situation.
The idea is to have a factory which produces the typical actions for REST-call lifecycle, and which can be used multiple times where the isolation scope is introduced by using the meta-property of the actions.

The problem appears to be introduced when a generic type P is introduced for the payload.

I am not sure if this is actually a thing of your library or if this is something to do with Typescript itself, but I hope that maybe you know what is happening.

Can't access action payload property in the reducer because of union types limitation

Hello guys,
I found that I can't access the action payload property in the reducer if I have multiple shapes of action payload. Given the situation like that:

// action types
export default {
  UPDATE_A: 'UPDATE_A',
  UPDATE_B: 'UPDATE_B'
};

// actions
const updateA = createStandardAction(types.UPDATE_A)<{
  a: string;
}>();

const updateB = createStandardAction(types.UPDATE_B)<{
  b: string;
}>();

// reducers
import { ActionType, getType } from 'typesafe-actions';
import actions from './actions';

type ActionType = ActionType<typeof actions>;

export function reducer (state = initialState, action: ActionType) {
  if (state === undefined) {
    return initialState;
  }

  switch (action.type) {
    case getType(Intl.updateA):
      return { ...state, ...action.payload }; // <- that's fine
    case getType(Intl.updateB):
      return { ...state, modified: action.payload.b }; // <- Property 'b' does not exist on type '{ a: string; } | { b: string; }'
    default:
      return state;
  }
}

That error come from accessing property in union of object types fails for properties not defined on all union members (ref).
Hence, I need to cast the payload with explicit action type to access it. However, that is not really elegant. Could anyone point me how to do in this case, @piotrwitek?

The potential solution is exploiting discriminated unions, but I still figure out how to use it with typesafe actions.

By the way, it seems that other people has the same problem.
#22 (comment)

Generic actions

Hello, I have a set of actions and reducers which I want to reuse in several places in my app. I want to store similar data which generates with those actions and reducers in different places of redux store, so I need to parametrize somehow actions and reducers, for example, to add some prefix to all actions names in a set. Is it a good and possible idea? Or should I use some data in action's payload to differentiate actions?

createAction doesn't support Symbol type

We are trying to use typed-actions in our project but according to our code style, we are using symbols everywhere. It would be good if type-actions will support this use case.

Suggestion: ability to create "routines"

First off, killer work on this library. And I only see it getting better and easier to use with TypeScript 2.8 on the horizon.

I've been trying to get this integrated into a new project we are starting that will have LOTS of async calls to a server. This means we have lots of actions that go through the trigger -> request -> success/fail flow.

Obviously this becomes boilerplate overload if I have to create 4 actions for every one of these. That said, I do need the ability to at the very least describe the action shape of the trigger and success actions.

I came across this library https://github.com/afitiskin/redux-saga-routines which is using redux-actions that makes creating a "routine" that goes through these steps much less boilerplate. That said, it's type safety is nowhere near the ease and cleanliness that typesafe-actions provides.

I've been taking a stab at merging the two concepts together, but I'm still a bit noobish around TypeScript, so making very slow progress.

Since in a sense you have already done the hard work of re-writing most of redux-actions I thought I might throw this out as a suggestion for a feature to add to this library.

If it's outside the scope, I totally get that, but perhaps a companion lib would make sense.

I'm happy to help, but leveraging some of your existing deep knowledge of the TypeScript world would go a long way.

I know this is not a problem unique to my use case, and I know many would benefit from a low boilerplate way to create a type safe action (or dynamically created set of actions) that describe the typical async flow.

Thoughts?

Type 'undefined' is not assignable to type 'RouterState' in strict mode.

I am using version 2.0 and "strict": true in tsconfig.json.

Code:

import { RouterState } from 'react-router-redux';
import { routerReducer } from 'react-router-redux';
import { combineReducers } from 'redux';
import { RootAction } from './actions';

export interface IRootState {
  router: RouterState;
}

export const rootReducer = combineReducers<IRootState, RootAction>({
  router: routerReducer,
});

Error:

Argument of type '{ router: Reducer<RouterState>; }' is not assignable to parameter of type 'ReducersMapObject<IRootState, RootAction>'.
  Types of property 'router' are incompatible.
    Type 'Reducer<RouterState>' is not assignable to type 'Reducer<RouterState, RootAction>'.
      Types of parameters 'state' and 'state' are incompatible.
        Type 'RouterState | undefined' is not assignable to type 'RouterState'.
          Type 'undefined' is not assignable to type 'RouterState'.

Workaround is to set "strictFunctionTypes": false in tsconfig.json (found it in ngrx/platform#709).

Reducer ReturnType doesn't work with Redux combineReducers

Could you flush out your example more? Unless I'm missing something, our reducers use combineReducers from Redux a lot and it complains when I try to use ReturnType to save on boilerplate code. Is there a way to get them to play nicely together? complains that A Action isn't same as PayloadAction. Using your new 2.0 stuff and loving it. Thanks for making it!

Add error property support when payload is instance of Error

I have become a little more familiar with the API's, and I really like them, they are super flexible and encourage use of FSA which is great.

I have been able to create an error action using createStandardAction but it wasn't clear how to do it with the other 2 API's.

The way our action creators were written previously, the meta value was the first parameter - so we would like to continue that, so we are writing them using action, but looking at action it isn't clear how to add the error: true property for an error action. (Actually now we are using createAction because we want getType)

Maybe action could be extended to have a 3rd parameter that just let you mix in whatever properties you wanted - or perhaps less flexible but better for FSA's would be to have an error parameter that let you set that property.

   fetchDataError: (widgetKey: string, error: Error) =>
        createStandardAction(FETCH_DATA_FAILURE).map((payload: Error, meta: string) => ({
            payload,
            meta: { widgetKey: meta },
            error: true as true
        }))(error, widgetKey)

Using the API in this way though breaks getType

I haven't tried the Async API's yet though. They might be useful here, but i still need the parameter ordering.

Cannot find module 'typesafe-actions'

I'm using 2.0.0-1 and I get the following error:

(1,43): Cannot find module 'typesafe-actions'.

The funny thing is that if I remove the import, save, add it again and save again, it runs without errors until I restart the build process.

This is the file that causes the error:

import { ActionsUnion, buildAction } from 'typesafe-actions';

const INCREMENT_CURRENT_QUESTION = 'INCREMENT_CURRENT_QUESTION';

export const betPublishActions = {
  incrementCurrentQuestion: buildAction(INCREMENT_CURRENT_QUESTION).empty()
};

export type BetPublishActions = ActionsUnion<typeof betPublishActions>;

My project is pretty fresh, created with the TS scripts of create-react-app.

My package.json:

{
  "name": "tipsterchat-webapp",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "node-sass-chokidar": "^1.2.2",
    "npm-run-all": "^4.1.2",
    "prettier": "^1.12.1",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-redux": "^5.0.7",
    "react-scripts-ts": "2.15.1",
    "redux": "^4.0.0",
    "redux-form": "^7.3.0",
    "typesafe-actions": "2.0.0-1"
  },
  "scripts": {
    "build-css": "node-sass-chokidar src/ -o src/",
    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
    "start-js": "react-scripts-ts start",
    "start": "npm-run-all -p watch-css start-js",
    "build-js": "react-scripts-ts build",
    "build": "npm-run-all build-css build-js",
    "test": "react-scripts-ts test --env=jsdom",
    "eject": "react-scripts-ts eject"
  },
  "devDependencies": {
    "@types/jest": "^22.2.3",
    "@types/node": "^8.10.11",
    "@types/react": "^16.3.13",
    "@types/react-dom": "^16.0.5",
    "@types/react-redux": "^5.0.19",
    "@types/redux-form": "^7.2.4",
    "typescript": "^2.8.3"
  }
}

The error keeps appearing after editing any other file (even scss) and I have to remove & add the import again.

action property type broken with @types/react-redux 6.0.0 + Redux 4 + React Redux 5.0.20

Version:
Redux 4 + React Redux 5.0.20 + typesafe-actions 2.0.2+ types/react-redux 6.0.0

For example:

todoAction:

const load = createAction("[Todo] Load Todo Item");

Container:

export interface Props {
  load: () => void;
}

const TodoContainer = ({load}: Props) => {
  return (
    <div>
      <button onClick={load}>load item</button>
    </div>
  );
};

export default  connect(null, {
  load: todoAction.load
})(TodoContainer);

Error:

[ts]
Argument of type '({ load }: Props) => Element' is not assignable to parameter of type 'ComponentType<Shared<{ items: Todo[];
 loading: boolean; } & { load: () => { type: "[Todo] Load To...'.
  Type '({ load }: Props) => Element' is not assignable to type 'StatelessComponent<Shared<{ items: Todo[]; loading: boolean; } & { load: () => { type: "[Todo] Lo...'.
    Types of parameters '__0' and 'props' are incompatible.
      Type 'Shared<{ items: Todo[]; loading: boolean; } & { load: () => { type: "[Todo] Load Todo Item"; }; }...' is not assignable to type 'Props'.
        Property 'load' is optional in type 'Shared<{ items: Todo[]; loading: boolean; } & { load: () => { type: "[Todo] Load Todo Item"; }; }...' but required in type 'Props'.

Expose getType() method for action creators

Hi! Thank you for this package, it's very useful.

Is it possible to expose getType() function or type property itself to the public API? I see that the implementation already here

getType: () => actionType,

I checked (TS 2.9.1) and right now it's possible to do things like this:

switch (action.type) {
  case someAction().type: { // I don't like this extra action creation
    // action typed correctly here
    break;
  }
}

So, I suppose it will work with getType()/type too and it will be a bit more simpler in use โ€” not need extra import for getType

Highlighting in code sandbox is broken

Hey! I just started learning react/redux stuff, so apology if the question is stupid.

When you open an example project reducer.ts file and wait for errors highlighting to appear you will see that action.type and more are marked as errors. I have the same behaviour in my local setup with vscode.

I assume its just tooling error but quite annoying one. Can it be somehow dealt with? Coming from scala I'm used to the editor not being able to follow my types but I hoped for the better in typescript :)

screenshot-codesandbox io 2018 06 03 09-31-36

How do you write an action creator with a default parameter?

Had you considered parameter defaults in your design?

Right now I am doing:

(payload: number = 1) => createStandardAction(CHANGE_VIEW)<number>()(payload)

But it seems like it could be improved upon so you don't have to repeat the payload type.

Thanks for the best redux action typings around!

Rename "ActionsUnion" to "ActionUnion"

This is nitpicking, but before v2 is completely set in stone, consider maybe renaming ActionsUnion to ActionUnion? I weren't able to find the exact English rule describing this (have no idea how to phrase the search query...), but it just feels more natural, same as "string array" versus "strings array", etc.

Edit

Relevant question on SE.

Warnings for "Cannot find source file '../src/*.ts" in Webpack with source-map-loader

Running Webpack 3 (and possibly older ones) with the source-map-loader plugin causes warnings like

Cannot find source file '../src/index.ts'

to be emitted.

This can be resolved by the user by modifying the module rule for source-map-loader in the webpack config to include

exclude: [path.resolve(__dirname, './node_modules/typesafe-actions')]

This is similar to a bug I found here - it appears their solution was to include the source files in the npm package.

Good package BTW - really helpful :)

Could we add "withType" to the public API

It seems that when you want to create your own custom action creators on top of the ones from typesafe actions, that you sometimes need to use the "withType" method from utils. Indeed the documentation itself states that it is useful for 3rd party action creators.

However, I had to replicate the code in my own code base, since it is not exposed. Do you think it could be exposed, just as the documentation suggests? :)

road to 1.0.0

  • refactor Playground Application to use typesafe-actions in all the relevant examples to make sure there are no issues
  • update Guide documentation and tutorials

Then the API will be fixed with semantic versioning and production ready

typesafe-actions v2.0 doesn't work well with @babel/plugin-transform-typescript

If I understand correctly, typesafe-actions v2.0 doesn't work well with @babel/plugin-transform-typescript.

I've been using @babel/plugin-transform-typescript in conjunction with react-app-rewired (via react-app-rewire-typescript-babel-preset so that I can use react-scripts from create-react-app rather than react-scripts-ts from create-react-app-typescript. I do this because I want to use all the upstream features in create-react-app that haven't made it to create-react-app-typescript yet.

I get a whole bunch of type errors in my IDE using [email protected] that I don't get when I try out your sample app. For example:

Creating actions like:

import {ActionsUnion, createStandardAction, getType} from "typesafe-actions";

const menuActions = {
    openMenu: createStandardAction('menu/OPEN')<void>(),
    closeMenu: createStandardAction('menu/CLOSE')<void>(),
};

type MenuActions = ActionsUnion<typeof menuActions>;

type RootAction = MenuActions | OtherActions;

type MenuState = {
    readonly show: boolean,
}

const menuReducer = (state = {show: false}, action: RootAction): MenuState => {
    switch (action.type) { // <---- one of the type errors (see below)
        case getType(menuActions.openMenu):
            return {show: true};
        case getType(menuActions.closeMenu):
            return {show: false};
        default:
            return state;
    }
};

Produces:

Property 'type' does not exist on type 'RootAction'.
Property 'type' does not exist on type '{ openMenu: B; closeMenu: B; }'.

I just asked a question in react-app-rewire-typescript-babel-preset to understand how this setup might differ from the one in your sample app. It seems the Babel TypeScript preset isn't using TypeScript at all and as a result there's a few language feature gotchas.

Can you help me determine if it's possible to use typesafe-actions v2.0 with @babel/plugin-transform-typescript rather than standard TypeScript compiler?

The size of the npm package is unrealistic.

NPM package size: 53.2 KB
NPM package size on disk: 112 KB

If I'm not mistaken the size of the actual solution is only about 5.5 KB, which should be something to boast about.

The problem is the dev dependencies. The yarn file alone is 100 KB. For a production application these dev dependencies should not be included in the node modules package, only the git repository.

Love this solution btw, Keep up the great work. Please be sure to ping me if there is anything I can do! ;)

Isnt createAction type parameter redundant when creatorFunction used?

Why createAction(string, fn) has first parameter pressent? Because i dont see difference between these two:

type TYPE = (param: any) => { type: "AAA", payload: { param: any } };

// yours
const action: TYPE = createAction("AAA", (param: any) => ({
  payload: { param },
  type: "AAA",
}));

// mine defined bellow
const action: TYPE = createAction2((param: any) => ({
  payload: { param },
  type: "AAA",
}));

export function createAction2<T extends string, AC extends (...args: any[]) => FluxStandardAction<T>>(
  creator: T | AC,
): AC & TypeGetter<T> {
  let type: T;
  let actionCreator: AC & TypeGetter<T>;

  if (typeof creator === 'string') {
    actionCreator = (() => ({ type })) as AC & TypeGetter<T>;
    type = creator;
  } else if (typeof creator === 'function') {
    actionCreator = creator as AC & TypeGetter<T>;
    type = actionCreator().type;
  } else {
    throw new Error('argument must be of type string or function');
  }

  (actionCreator as TypeGetter<T>).getType = () => type;
  return actionCreator;
}

only thing, that is funky is calling creatorFunction to determine action type ...

Why? Lazynest :) - currenly i need to write action type twice ....

PS: Tryied to create PR, but same code fails to compile in fork project (compiles in my test project)

add tests for runtime edge cases

Identify edge cases and incorrect inputs during runtime execution, especially when used without types with JS making it more open to unexpected input values

Include the returntypeof helper in this library

Using this library tends to require the returntypeof helper, but that is in another library, which is fine, but that one broke for Typescript 2.7 which makes me think that maybe returntypeof belongs with this code more, or at least in addition.

is here a way to use `redux-promise-middleware` together?

I want to use redux-promise-middleware and this lib in my project, but I don't know is here a way to assert action when I use redux-promise-middleware, and assert type T of Promise<T>?

redux-promise-middleware will re-dispatch three actions, with suffix '_PENDING', '_FULFILLED', '_REJECTED'.

Documentation for upgrade process

Hi,
Thanks for this. It's really useful. I'm using it in ignite-typescript-boilerplate, and ran into a problem with the API changes between 1.0.0-beta5 and 1.0.0-rc0, specifically the change from .type to getType(). It would be great if you could give some details about how to upgrade from ts-redux-actions to typesafe-actions, and if there are any other breaking changes.

Type inference issue for "createAction" API

Added missing test scenario for createAction when payload is undefined and only meta should be returned.
Test case:

it('with meta', () => {
    const action = createAction(types.WITH_META, resolve => {
      return (token: string) => resolve(undefined, token);
    });
    const actual: { type: 'WITH_META'; meta: string } = action('token'); // Error:   Property 'meta' is missing in type '{ type: "WITH_META"; }'.

    expect(actual).toEqual({ type: 'WITH_META', meta: 'token' });
  });

suggestion: createAction with just creatorFunction argument

createAction called with a creatorFunction has an unused and not-DRY typeString argument. Could it just be inferred from the return type of the creatorFunction?

E.g.:

export declare function createAction<T extends string, AC extends (...args: any[]) => {
    type: T;
}>(creatorFunction: AC): AC & TypeGetter<T>;

then used like this:

const actions = {
  ping: createAction((arg: number) => ({
    type: 'PING',
    arg,
  })),
};

Question regarding action.type

Hey there, this looks like a cool project and it's something I would like to use.

This is probably a dumb question but how does the type-narrowing work in this example:

switch (action.type) {
  // getter on every action creator function:
  case increment.getType!():
    return state + 1;
  // alternative helper function for more "FP" style:
  case getType(increment):
    return state + 1;

  default: return state;
}

I'm guessing you have to maintain a union type for all of your actions?

Something like

type Action = typeof increment | typeof decrement | ...

How to handle filtering in epic with more than one action?

In the following code snippet it lets 2 actions in but then TypeScript complains trying to access payload on clearVisitedStream because authLogoutSuccess action is a simple action without a payload.

export const authLogoutSuccess = buildAction('AUTH_LOGOUT_SUCCESS').empty();
export const clearVisitedStream = buildAction('CLEAR_VISITED_STREAM').fsa((channelId?: number) => ({ channelId }));

const clearChannelVisitHistory: Epic<RootAction, IRootState> = action$ =>
    action$
        .filter(isActionOf([clearVisitedStream, authLogoutSuccess]))
        .mergeMap(action => {
            if (isActionOf(authLogoutSuccess) || (isActionOf(clearVisitedStream) && action.payload.channelId === null)) {

gives typescript error:

Property 'payload' does not exist on type 'EmptyAction<"AUTH_LOGOUT_SUCCESS"> | PayloadAction<"CLEAR_VISITED_STREAM", { channelId: number; }>'.
Property 'payload' does not exist on type 'EmptyAction<"AUTH_LOGOUT_SUCCESS">'.

isActionOf should accept array of actions

isActionOf currently can accept only one action, the common use case is to accept an array of actions like:

action$
  .filter(isActionOf([authActions.loginSuccess, authActions.logoutSuccess]))

Async-flow with params

Hello, I'm trying to make an API call with params but I achieved only empty API call for now. Here is code:

Actions:

import { createAsyncAction } from 'typesafe-actions';
import {ICats} from '/api/cats';

export const FETCH_CATS_REQUEST = 'cats/FETCH_CATS_REQUEST';
export const FETCH_CATS_SUCCESS = 'cats/FETCH_CATS_SUCCESS';
export const FETCH_CATS_ERROR = 'cats/FETCH_CATS_ERROR';

export const fetchCats = createAsyncAction(
    FETCH_CATS_REQUEST,
    FETCH_CATS_SUCCESS,
    FETCH_CATS_ERROR
) <void, ICats, Error> ();

Call dispatch:

store.dispatch(fetchCats.request());

My epics:

const fetchCatsFlow: Epic<Types.RootAction, Types.RootAction, Types.RootState> = (action$) =>
    action$.pipe(
        filter(isActionOf(fetchCats.request)),
        switchMap(() =>
            fromPromise(Cats.getDataFromAPI()).pipe(
                map(fetchCats.success),
                catchError(pipe(fetchCats.failure, of))
            )
        )
    );

API:

export const Cats = {
    getDataFromAPI: () => $http.get('/cats').then(res => {
        return res.data as any;
    }),
};

I tried to call dispatch like this:
store.dispatch(fetchCats.request(data));
but request expects 0 params. And like this:
store.dispatch({ type: fetchCats.request(), data});

Probably I should do something like this:

const fetchCatsFlow: Epic<Types.RootAction, Types.RootAction, Types.RootState> = (action$) =>
    action$.pipe(
        filter(isActionOf(fetchCats.request)),
        switchMap((params) =>
            fromPromise(Cats.getDataFromAPI(params)).pipe(
                map(fetchCats.success),
                catchError(pipe(fetchCats.failure, of))
            )
        )
    );
export const Cats = {
    getDataFromAPI: (params) => $http.get('/cats', {
params: {
type: params.type
}
}).then(res => {
        return res.data as any;
    }),
};

but I don't know how to transform this actions properly. In docs: https://github.com/piotrwitek/typesafe-actions#--the-async-flow there is no such an example with passing params.

Doc: specify that a polyfill for Object.assign is needed

Old browsers (IE 11, Safari on iOS 8) do not support Object.assign but it is used in typesafe-actions.
Many other libraries are using a polyfill (short custom implementation or external polyfill bundled using Babel). As this package targets es5 (in tsconfig.json), Object.assign is not transpiled and generate errors in those old browsers.

I am fine with this package targeting es5, but it should be noted explicitely in the README, and a list of needed polyfills should be provided.
For example, here is how React does it: https://reactjs.org/docs/javascript-environment-requirements.html

I tested my app with Object.assign, Map, Set and Symbol polyfills (the 3 latest needed for React) and it worked fine on iOS 8 & IE 11, so I guess Object.assign is the only one needed (or maybe Symbol too?) for typesafe-actions.

Doesn't export createStandardAction in 2.0.0-rc

I found that there isn't createStandardAction method when Installing typedafe-actions from npm.

In the release, it only export the following

export * from './types';
export * from './get-type';
export * from './is-action-of';
export * from './create-action';
export * from './create-symbol-action';
export * from './build-action';

d.ts compilation errors

Using v2.0.4 I'm seeing the following compilation errors (although node_modules are excluded from compilation, these files are included due to typescript's module resolution):

Import:

import { action } from "typesafe-actions";

Errors:

ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(2,25):
TS2323: Cannot redeclare exported variable 'isOfType'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(5,5):
TS7028: Unused label.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(5,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(6,5):
TS7028: Unused label.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(6,9):
TS2693: 'never' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(7,25):
TS2323: Cannot redeclare exported variable 'isOfType'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(10,5):
TS7028: Unused label.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(10,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(11,5):
TS7028: Unused label.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/is-of-type.d.ts(11,9):
TS2693: 'never' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(11,86):
TS2304: Cannot find name 'M'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(12,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(14,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(15,11):
TS2304: Cannot find name 'M'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(16,5):
TS2304: Cannot find name 'M'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(17,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(18,14):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(20,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(21,14):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(22,11):
TS2304: Cannot find name 'M'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,122):
TS2693: 'B' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,132):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,142):
TS2693: 'B' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,152):
TS2693: 'NoArgCreator' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,165):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,170):
TS2693: 'PayloadCreator' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,185):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,190):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,196):
TS2693: 'PayloadMetaCreator' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,215):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,220):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(40,226):
TS2304: Cannot find name 'M'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(41,140):
TS2693: 'B' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(41,150):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(41,160):
TS2693: 'B' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(41,176):
TS2693: 'MapAction' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(42,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(43,7):
TS2709: Cannot use namespace 'R' as a type.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(43,25):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(43,32):
TS2693: 'MapAction' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(44,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(45,7):
TS2709: Cannot use namespace 'R' as a type.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(45,25):
TS2304: Cannot find name 'P'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(45,37):
TS2304: Cannot find name 'M'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(45,44):
TS2693: 'MapAction' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(46,11):
TS2304: Cannot find name 'T'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(47,7):
TS2709: Cannot use namespace 'R' as a type.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,81):
TS2693: 'ActionCreator' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,97):
TS2304: Cannot find name 'ReturnType'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,108):
TS2304: Cannot find name 'ActionCreatorOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,130):
TS2304: Cannot find name 'ActionCreatorOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,157):
TS2304: Cannot find name 'object'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,166):
TS2365: Operator '>' cannot be applied to types 'boolean' and 'any[]'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,166):
TS2693: 'ActionCreatorMap' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,183):
TS2304: Cannot find name 'ActionCreatorOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,203):
TS2304: Cannot find name 'keyof'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,209):
TS2304: Cannot find name 'ActionCreatorOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(57,231):
TS2693: 'never' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(58,88):
TS2693: 'any' only refers to a type, but is being used as a value here.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(58,94):
TS2304: Cannot find name 'ReturnType'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(58,105):
TS2304: Cannot find name 'ReducerOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(58,121):
TS2304: Cannot find name 'ReducerOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(58,142):
TS2304: Cannot find name 'object'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(59,5):
TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(59,6):
TS2304: Cannot find name 'K'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(59,11):
TS2304: Cannot find name 'keyof'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(59,17):
TS2304: Cannot find name 'ReducerOrMap'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(59,55):
TS2304: Cannot find name 'K'.
ERROR in /Users/josh/DEV/censinet/client/node_modules/typesafe-actions/dist/types.d.ts(60,5):
TS2693: 'never' only refers to a type, but is being used as a value here.

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.