Git Product home page Git Product logo

deox's Introduction

Deox

Functional type-safe Flux standard utilities

License Build Status Coverage Status Semantic Release Latest Release PRs welcome

The only completely functional type-safe approach to Flux that its main goals are to diminish types verbosity, repetition and complexity without losing any type information (type-safe alternative of redux-actions).

Behold, the art of Deox:

Deox counter example

Highlights

  • Minimalist (almost no import cost) - checkout Bundle Phobia.
  • Simple - focused on self-declarative API.
  • Secure - complete test-suits for all of the edge and corners.

Motivation

The most common complaint about Flux is how it makes you write a lot of boilerplate. Also, existing solutions like redux-actions or redux-starter-kit are not type-safe by the idea (although there are some other ideas with classes 😱).

So, this is where Deox takes place to make maintenance of Flux architecture simpler and more readable by sticking to functional programming paradigm.

Installation

You can install Deox package by running:

# YARN
yarn add deox

# NPM
npm install deox

Typescript tip: notice that Deox internally uses some ES2015 type definitions to represent better developer experience. So if you are using Typescript and targeting es5, be sure es2015 lib has been added in tsconfig.json:

{
  "compilerOptions": {
    ...
    "target": "es5",
    "lib": ["es2015"]
  }
}

The Deox NPM package contains a CommonJS build that can be use with Node.js or module bundlers (e.g. Rollup, Webpack, etc.). it also includes an ESM build that works well with tree-shaking.

If you don't use module bundler, it's also fine. The Deox NPM package also includes a production minified UMD build that makes Deox available as global variable called window.Deox; you can add it simply to your page via following script tag:

<script src="https://unpkg.com/deox@latest"></script>

Usage

import { createActionCreator, createReducer } from 'deox'

const increment = createActionCreator('INCREMENT')
const decrement = createActionCreator('DECREMENT')
const reset = createActionCreator('RESET', resolve => (count: number) => resolve(count))

const defaultState = 0

const counterReducer = createReducer(defaultState, handleAction => [
    handleAction(increment, state => state + 1),
    handleAction(decrement, state => state - 1),
    handleAction(reset, (_state, { payload }) => payload),
])

counterReducer(undefined, increment()) //=> 1
counterReducer(undefined, decrement()) //=> -1
counterReducer(3, reset(0)) //=> 0

Documentation

FAQ

Why not redux-actions, redux-starter-kit ?

Both redux-actions and redux-starter-kit are neat and almost similar to each other. Actually deox is similar to those projects in the idea, but not in implementation and promise. The main goal of deox is to use the full power of type-safety and type inferring in typescript. If you have some experience with those libraries, the following piece of code should be familiar for you:

type Actions
  = ReturnType<typeof addTodo>
  | ReturnType<typeof removeTodo>
  | ReturnType<typeof editTodo>

const todosReducer = createReducer<State, Actions>(...)

This is horrible; Why define a type like actions that a reducer can handle?! It's completely obvious which actions a reducer handles.

On another hand there is a big problem with the pattern that redux-actions and redux-starter-kit follows. it's lack of correct type for action handler:

const todosReducer = createReducer<State, Actions>(defaultState, {
  [addTodo]: (state, action) => {...}, // action: Actions
  [removeTodo]: (state, action) => {...}, // action: Actions
  [editTodo]: (state, action) => {...}, // action: Actions
})

Type of action parameter in addTodo action handler is overall Actions type. It's inaccurate!

And this is where Deox comes in action and practice:

const todosReducer = createReducer(defaultState, handleAction => [
  handleAction(addTodo, (state, action) => {...}), // action: AddTodoAction
  handleAction(removeTodo, (state, action) => {...}), // action: RemoveTodoAction
  handleAction(editTodo, (state, action) => {...}) // action: EditTodoAction
])

That's it. Thanks to typescript type inferring there is no type verbosity at all. You can be sure todos reducer have the proper type of state and actions that it can handle. And every action handler's type is just what it should be. It's completely safe and correct!

What's the difference with typesafe-actions ?

The typesafe-actions is a great project that Deox carries huge inspiration from that. But typesafe-actions doesn't have any plan for a complete set of utilities (specially reducers); It's all about actions and action creators.

Versioning

Deox uses Semantic Versioning 2.0.0

Contributing

Please read through our contributing guidelines.

Inspiration

  • redux-actions - Flux Standard Action utilities for Redux
  • typesafe-actions - Typesafe Action Creators for Redux / Flux Architectures (in TypeScript)

License

Deox is released under MIT license.

deox's People

Contributors

akurganow avatar dependabot[bot] avatar greenkeeper[bot] avatar haaxor1689 avatar jazzmanpw avatar kotarella1110 avatar michael-1 avatar santojambit avatar semantic-release-bot avatar the-dr-lazy 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

deox's Issues

Handler map factory should handle optimistic action type

According to #2, createHandlerMap should be capable to handle optimistic action creators.

Detailed Description

Yet, optimistic actions should be handled separately with multiple createHandlerMap call. So there would be a good point to reduce boilerplates.
Although, cancel is not part of observer protocol but this type practically is important and should be handled in many cases (isFetching entity, etc.) in the reducers.

Possible Implementation

createHandlerMap(addFoo, {
  next(state, action) {},
  error(state, action) {},
  complete(state, action) {},
  cancel(state, action) {}
})

Make "defaultState" (first argument of createReducer) optional

Motivation

In Redux related reducers it's helpful to make defaultState of the reducer required but in React's useReducer hook it's useless and make repetition of default state both in createReducer and useReducer.

Current usage of Deox with useReducer is like the following:

const increment  = createActionCreator('INCREMENT')
const decrement = createActionCreator('INCREMENT')

const counterDefaultState = 0

const counterReducer = createReducer(counterDefaultState, handleAction => [
  handleAction(increment, state => state + 1),
  handleAction(decrement, state => state - 1),
])

function Counter() {
  const [state, dispatch] = React.useReducer(counterReducer, counterDefaultState)

  const actions = bindActionCreators({ increment, decrement }, dispatch)

  // ..
}

With this feature, it becomes like the following:

const increment  = createActionCreator('INCREMENT')
const decrement = createActionCreator('INCREMENT')

const counterReducer = createReducer<number>(handleAction => [
  handleAction(increment, state => state + 1),
  handleAction(decrement, state => state - 1),
])

function Counter() {
  const [state, dispatch] = React.useReducer(counterReducer, 0)

  const actions = bindActionCreators({ increment, decrement }, dispatch)

  // ..
}

Unfortunately, for now, typescript doesn't support generic currying which explained in microsoft/TypeScript#30123 and microsoft/TypeScript#10571.

Basic example

If Typescript supports generic currying, this feature can be like the following:

const reducer = createReducer<State>(handleAction => [ /* ... */ ])

Without Typescript support of generic currying, this feature can be like the following:

const reducer = createReducer<State>()(handleAction => [ /* ... */ ])

It's important to notice if a reducer created without a default state then the state (first) argument of the returning reducer should be required.

Document motivation of the library

The motivation of the Deox should be documented in readme and introduction page of the documentation site.
The motivation topic at first should explain the boilerplate problem in the Flux and then should provide a brief type-safety problem of other similar libraries.

prev state vs next state

createReducer infers the next state from the actions instead of using the previous state, and I wonder why that is, because it creates weird types on the State interfaces.

For example, when I try this

For example, with the following sample code:

import { createReducer, createAction } from 'deox';

interface TestType {
    foo: string;
    bar: number;
}

interface TestState {
    [s: string]: TestType;
}

const initialState: TestState = {};

export const addTest = createAction('ADD_TEST', (resolve) => (id: string, type: TestType) => resolve({ id, type }));


const testReducer = createReducer(initialState, (handleAction) => [
    handleAction(addTest, (state, action) => ({
        ...state,
        [action.payload.id]: {
            ...state[action.payload.id],
            ...action.payload.type,
        },
    })),
]);

type CombinedState = ReturnType<typeof testReducer>;

I get this as CombinedState (when inspecting via vscode):

type CombinedState = {
    [x: string]: TestType | {
        foo: string;
        bar: number;
    };
}

And this gets worse with more reducers and actions.

Should the initialState not have the same type definition as anything that comes out of the reducer? Why the separation of both?

Obviously, I can create a wrapper function for this:

function createReducerClean<TPrevState, THandlerMap extends HandlerMap<TPrevState, any, any>>(defaultState: TPrevState, handlerMapsCreator: (handle: CreateHandlerMap<TPrevState>) => THandlerMap[]): (state: TPrevState | undefined, action: InferActionFromHandlerMap<THandlerMap>) => TPrevState {
    return createReducer(defaultState, handlerMapsCreator);
}

But aside from this being additional maintenance work, I wonder why this is done in the first place.

Btw, thanks for deox, it helps a lot.

Why "createReducer" return type isn't a deox/dist/types/Reducer?

For those who received a incomplete issue in email, I am sorry, I have hit enter accidentally before finishing.

Now, to the issue:

I am trying to pass a Reducer as a parameter to a method, but I get a compile error about the types.

Please let me know if I am using the library wrong:

Error:(61, 27) TS2345: Argument of type '(state: DeepImmutableArray | undefined, action: { type: string; payload: Param; } | { type: string; payload: { index: number; param: Param; }; } | { type: string; payload: { fromIndex: number; toIndex: number; }; }) => Param[] | DeepImmutableArray<...>' is not assignable to parameter of type 'Reducer<DeepImmutableArray, { type: string; payload: Param; } | { type: string; payload: { index: number; param: Param; }; } | { type: string; payload: { fromIndex: number; toIndex: number; }; }>'.
Types of parameters 'state' and 'prevState' are incompatible.
Property '[Symbol.iterator]' is missing in type 'DeepImmutableObject<DeepImmutableArray>' but required in type 'DeepImmutableArray'.

export function createParamTableReducer(
  actions: ParamTableActionCreators
): Reducer<Param[]> {
  const deoxReducer = createReducer([] as Param[], (handle) => [
    handle(actions.addParam, (state, { payload }) =>
      produce(state, (draft: Draft<Param[]>) => {
        draft.push(payload);
      })
    ),
    handle(actions.removeParam, (state, { payload }) =>
      produce(state, (draft: Draft<Param[]>) => {
        draft.splice(payload.index, 1);
      })
    ),
    handle(actions.updateParam, (state, { payload }) =>
      produce(state, (draft: Draft<Param[]>) => {
        draft[payload.index] = payload.param;
      })
    ),
    handle(actions.reorderParams, (state, { payload }) =>
      produce(state, (draft: Draft<Param[]>) => {
        draft.splice(payload.fromIndex, 1);
        draft.splice(payload.toIndex, 0, state[payload.fromIndex]);
      })
    ),
  ]);

  return normalizeReducer(deoxReducer);
}
import { Reducer } from "deox/dist/types";
import { Action, Reducer as ReduxReducer } from "redux";

export function normalizeReducer<T, A extends Action>(
  deoxReducer: Reducer<T, A>
): ReduxReducer<T, A>

Why doesn't "createReducer" uses the existing Reducer type as a returnType?

Getting started documentation page

The documentation should explain the following topics:

  • A quick introduction to the getting strated topic
  • Installation and its related tips
  • A basic usage like a counter app
  • Link to the source code of more examples in the examples directory at the root of the repo
  • Link to API reference

Passing falsy payload leads to action without payload property

I've started using this library on a fresh project, looks really promising. However I've noticed one issue. When passing falsy values to action creator it seems to not get attached to the created action.

Example:

export const setOpen = createAction(
  'SET_OPEN',
  resolve => (payload: boolean) => resolve(payload)
);

setOpen(true)
// { type: 'SET_OPEN', payload: true } <- all good

setOpen(false)
// { type: 'SET_OPEN' } <- missing payload property
export const setCount = createAction(
  'SET_COUNT',
  resolve => (payload: number) => resolve(payload)
);

setCount(1)
// { type: 'SET_COUNT', payload: 1 } <- all good

setCount(0)
// { type: 'SET_COUNT' } <- payload is missing

Seems to be related to here:
https://github.com/thebrodmann/deox/blob/b7be530ec77ce81f24692f7438daeebfea7b74a7/src/action.ts#L81

Passing any falsy value would make payload not appear on created action. Maybe action function could at least check if that value is undefined and then ignore payload, otherwise just add payload?

Make rxjs/operators in UMD bundle an external dependency

rxjs should works as a peer dependency if we want to use something like redux-observable with redux or ngrx. The filter operator from rxjs/operators should be an external dependency in UMD bundle that can be reached by rxjs.operators.filter global variable.

Create a "actionCreatorFactory" function

Very often I need to make reusable redux components, and also often these components state appear more than once in the Redux store.

To solve this I usually follow this pattern:

export function createParamActionCreators(paramTableName: string) {
  return {
    setParams: createActionCreator(
      `[${paramTableName}] SET_PARAMS`,
      (resolve) => (param: Param[]) => {
        return resolve(param);
      }
    ),

    addParam: createActionCreator(
      `[${paramTableName}] ADD_PARAM`,
      (resolve) => (param: Param) => {
        return resolve(param);
      }
    ),
    ...
  };
}

type ParamTableActionCreators = ReturnType<typeof createParamActionCreators>;

export function createParamTableReducer(actions: ParamTableActionCreators) {
  return createReducer([] as Param[], (handle) => [
    handle(actions.setParams, (_state, { payload }) => payload),
    handle(actions.addParam, (state, { payload }) =>
      produce(state, (draft: Draft<Param[]>) => {
        draft.push(payload);
      })
    ),
    ...
  ]);
}

I am trying to implement it myself, you can see my struggle to make a prototype here

I am opening this to see what people think about this, I think this is mostly an edge case of mine, not sure if it is worth putting on a library

An in-range update of commitlint is breaking the build 🚨

There have been updates to the commitlint monorepo:

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the commitlint group definition.

commitlint is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ci/circleci: test: Your tests failed on CircleCI (Details).
  • test-build-release: * test - Failed
  • build - Blocked

Commits

The new version differs by 39 commits.

  • c17420d v8.1.0
  • ca19d70 chore: update dependency lodash to v4.17.14 (#724)
  • 5757ef2 build(deps): bump lodash.template from 4.4.0 to 4.5.0 (#721)
  • 5b5f855 build(deps): bump lodash.merge from 4.6.0 to 4.6.2 (#722)
  • 4cb979d build(deps): bump lodash from 4.17.11 to 4.17.13 (#723)
  • a89c1ba chore: add devcontainer setup
  • 9aa5709 chore: pin dependencies (#714)
  • c9ef5e2 chore: centralize typescript and jest setups (#710)
  • c9dcf1a chore: pin dependencies (#708)
  • 6a6a8b0 refactor: rewrite top level to typescript (#679)
  • 0fedbc0 chore: update dependency @types/jest to v24.0.15 (#694)
  • 0b9c7ed chore: update dependency typescript to v3.5.2 (#695)
  • 4efb34b chore: update dependency globby to v10 (#705)
  • 804af8b chore: update dependency lint-staged to v8.2.1 (#696)
  • 9075844 fix: add explicit dependency on chalk (#687)

There are 39 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Optimistic action creator factory

action creator creation for optimistic flow is a good point to reduce boilerplate with a factory function.

Detailed Description

Each typical optimistic flow has 4 potential types of action:

  • next: starts an async flow
  • error: returns the error of the async flow rejection
  • complete: returns the successful result of the async flow
  • cancel: cause termination of the async flow

According to the nature of action types, all of these actions maybe contains payload and/or meta.

Make createActionCreator simpler by removing `resolve` callback from executor

From TypeScript 3.0 we are able to infer function's parameters with their names. So now we can remove resolve callback from executor in createActionCreator to make the API simpler.

Example:

// (name: string) => { type: "FETCH_TODOS"; }
createActionCreator("FETCH_TODOS");

// (name: string) => { type: "ADD_TODO"; payload: string; }
createActionCreator("ADD_TODO", (name: string) => ({ payload: name }));

There is one caveat point that has been encountered in #108 and that's type property in return type of callable (2nd argument of proposed createActionCreator). One possible solution for the mentioned problem which I think is ideal is to set the type of type property to undefiend.

// Types of property 'type' are incompatible.
// Type 'string' is not assignable to type 'undefined'.ts(2345)
createActionCreator("ADD_TODO", (name: string) => ({ type: 'MISTAKE', payload: name }));

We can call the callable's return type TypelessAction as it's type should be undefined.

Usage with redux-thunk

Hi! Thanks for this awesome library!

I'm new to Redux and had successfully done some work with redux-thunk in simple action creators.

However, the resolve in createAction confuses me. Is it the same resolve as in Promise, which makes me wonder if I can use async functions in executor?

Could you please make an example of how it works with redux-thunk or other async action creators?

Thank you!

Can't export reducer declaration with TSC

Hi there, thanks for a great library - much better than trying to use react-actions.

There's an issue when declaration: true is set in tsconfig.json: attempts to export reducers created by createReducer() throws the following error: TS4023: Exported variable 'reducer' has or is using name 'DeepImmutableArray' from external module "C:/Users/scbird/Documents/Development/pso/node_modules/deox/dist/types" but cannot be named.

Is it possible to export the DeepImmutable* types so that createReducer() can be used in projects that export declaration files?

As a test case, https://github.com/thebrodmann/deox/blob/master/examples/tasks/src/store/tasks.ts fails to compile with declaration: true

Make "DeepImmutable" optional.

Greetings.

I am having some trouble passing the state to other functions:

export interface HttpUriStore {
  rawUrl: string;
  query: Param[];
  method: string;
}

export function updateUrl(
  state: HttpUriStore,
  index: number,
  newParam: Param
): string

export const HttpUriReducer = createReducer(uriDefaultState, (handle) => [
  handle(updateRawUrl, (state, { payload }) => {
/*   TS2345: Argument of type 'DeepImmutableObject<HttpUriStore>' is not assignable to parameter of type 'HttpUriStore'.
  Types of property 'query' are incompatible.
    Type 'DeepImmutableArray<Param>' is missing the following properties from type 'Param[]': pop, push, reverse, shift, and 6 more.
                v*/
    updateUrl(state, 0, {} as Param);
    return produce(state, (draft) => {
      draft.rawUrl = payload;
    });
  }),
])

Is there a way to "disable" The DeepImmutable type wrapper? I understand that it can prevent acidental mutations, but I think it should be optional

I tried the following:

import { DeepImmutable } from "deox/dist/types";

function recalculateParams(
  state: DeepImmutable<HttpUriStore>,
  index: number,
  newParam: Param
) {

The import gives the following warn: unable to resolve path to module, but that is a @typescript-eslint bug I think, because it compiles.

Also as a minor incovenience: I am using immer, and because of the immutable wrapper I need to specify the draft type( (draft: Draft<State>)=> ) when I using splice or other commands inside the draft function.

Documentation site

Deox needs a good documentation site for better SEO and DX.

I found docz as a good documentation site generator that has almost no cost.

Rename createAction to createActionCreator and make createAction an alias for it

Personally, I choose more declarative names over shorter. But as said, this is a personalized issue and some other developers prefer shorter names. Also, other similar libraries like redux-actions use createAction name for the action creator factory function! So this made a wrong convention between developers and at least we should adhere to this convention for now.

For the above reasons, I think the best way to cover both styles for now is to use declarative names inside the library.

For this purpose the createAction function should rename to createActionCreator and use createAction as an alias for createActionCreator to not make a break change.

Strange return type of DeepImmutable and Immutable for ReadonlyArray and ReadonlyMap

Summary

By nested DeepImmutable or directly passing ReadonlyArray and ReadonlyMap to DeepImmutable and Immutable types results in DeepImmutableObject<ReadonlyArray> and DeepImmutableObject<ReadonlyMap> which should be neutralized by the type itself.

Steps to reproduce

For ReadonlyArray:

type A = DeepImmutable<DeepImmutable<string[]>>

For ReadonlyMap:

type B = DeepImmutable<DeepImmutable<Map<'key', 'value'>>>

Expected result(s)

For ReadonlyArray:

type A = DeepImmutableArray<string>

For ReadonlyMap:

type B = DeepImmutableMap<'key', 'value'>

Actual result(s)

For ReadonlyArray:

type A = {
    readonly [x: number]: string;
    readonly length: number;
    readonly toString: () => string;
    readonly toLocaleString: () => string;
    readonly concat: {
        (...items: ConcatArray<string>[]): string[];
        (...items: (string | ConcatArray<string>)[]): string[];
    };
    ... 16 more ...;
    readonly includes: (searchElement: string, fromIndex?: number | undefined) => boolean;
}

For ReadonlyMap:

type B = {
    readonly forEach: (callbackfn: (value: "b", key: "a", map: ReadonlyMap<"a", "b">) => void, thisArg?: any) => void;
    readonly get: (key: "a") => "b" | undefined;
    readonly has: (key: "a") => boolean;
    readonly size: number;
    readonly entries: () => IterableIterator<...>;
    readonly keys: () => IterableIterator<...>;
    readonly values: () => IterableIterator<...>;
}

Solution

Add conditions to check ReadonlyArray and ReadonlyMap in DeepImmutable and Immutable types.

2.1.2 breaking change on ActionCreator type

I have a situation where I need to define the ActionCreator type manually:

export type ShowNotificationAction = Action<typeof SHOW_NOTIFICATION, { type: 'error' | 'success'; labelKey: TranslationKey }>;
export const showNotification: ActionCreator<ShowNotificationAction> = createAction(SHOW_NOTIFICATION,
    (resolve) => (type: 'error' | 'success', labelKey: TranslationKey) => resolve({ type, labelKey }));

This works with 2.1.0, but not with 2.1.2. In 2.1.2, I get this error:

Argument of type 'ActionCreator<{ type: "CORE/SHOW_NOTIFICATION"; payload: { type: "error" | "success"; labelKey: "..." | "...." | ...; }; }>' is not assignable to parameter of type 'ActionCreator<string> | ActionCreator<string>[]'.

Trivia:
The reason why I need to do this is because TranslationKey is created via declaration merging and typescript currently has a bug, which expands the keys into the declaration file:
microsoft/TypeScript#27171

I.e. instead of generating a type (type: 'error' | 'success', labelKey: TranslationKey) => ..., typescript generates a type (type: 'error' | 'success', labelKey: 'Key1' | 'Key2' | ... | 'KeyN') => ..., which breaks the declaration merging pattern I need in this case.
By defining the ActionCreator type manually as seen above, I can avoid this issue.

Usage with redux-saga

I use redux-saga. Currently, I have to define a type in addition to my action:

export type CreateUserRequestedAction = Action<
  ActionTypeKeys.CreateUserRequested,
  User
>;

export const createUserRequested = createAction(
  ActionTypeKeys.CreateUserRequested,
  resolve => (user: User): CreateUserRequestedAction => resolve(user),
);

I need this CreateUserRequestedAction type to use this later in my saga for the action parameter:

export function* userEditSaga(action: CreateUserRequestedAction) {
  try {
    // define type explicitly because of lack of return types of generators:
    // https://github.com/redux-saga/redux-saga/issues/1286#issuecomment-482866473
    const fetchedUser: User = yield call(userEditRequest, {
      firstName: 'john',
      lastName: 'doe',
    });
    yield put(createUserSucceeded(fetchedUser));
  } catch (error) {
    yield put(createUserFailed());
  }
}

export const userSagas = [
  takeLatest(ActionTypeKeys.CreateUserRequested, userEditSaga),
];

Is there a better solution for this or do I need this type to gain type-safety and autocompletion in my userEditSaga?

Thank you in advance!

Kind regards
Nico

Usage with immer

Hello,

Wonderful library!

Do you have plans to integrate immer (https://github.com/mweststrate/immer)?
That would make writing reducers very easy

I'm trying to create my own version of createReducer that uses immer but without luck, maybe you know how to use your library with immer?

Thanks

Add ofType rxjs operator

In redux-observable there is an operator that is a higher order operator for the filter operator but it only can get an action type and doesn't work with action creators. In deox we should export an operator for this job that works correctly with both action type, action and action creator.

Possible Implementation

import { filter } from 'rxjs/operators'

const ofType = <TC extends ActionCreator | AnyAction | string>(typeContainer: TC) => {
  const targetType = typeof typeContainer === 'string' ? typeContainer : getType(typeContainer)

  return filter(({ type }) => type === targetType)
}

handleAction allows you to return a state object with any additional properties

This should fail because otherNum is not part of the State interface:

interface State {
  num: number
}

export const initialState: State = {
  num: 0
};

export const appsReducer = createReducer(initialState, handleAction => [
  handleAction(actions.getApps.success, () => ({
    num: 0,
    otherNum: 0 // adding this property should trigger a warning
  })),
]);

Typescript only displays a warning when the return type of State is explicitly defined but it would be nice to have it happen automatically

export const appsReducer = createReducer(initialState, handleAction => [
  handleAction(actions.getApps.success, (): State => ({ // specify the return type
    num: 0,
    otherNum: 0 // this triggers a warning correctly because we added `State` as the return type
  })),
]);

Add configureStore helper

The idea is now implemented in redux-starter-kit.

There should not be any difference between deox and redux-starter-kit in the implementation of configureStore. So I think it's enough to just re-export the configureStore helper.

Bundle size

Hi,

great library, but the bundle footprint seems to be rather large. In fact, in my small app it's the second largest after react-dom, even larger than redux itself.

webpack-bundle-analyzer gives me the following non-gzipped values:
react-dom: 103.89KB
redux: 6.04KB
deox: 40.09KB

I saw issue #42 which was just resolved. Maybe this will fix this issue too, but in general I'm not sure if maybe this library could be optimised some more. Maybe even add better support for tree-shaking. Sadly I'm not an expert on that topic myself.

How it will lead with react-hooks?

I liked a lot it but now that there is a new coding approach with Hooks, how Deox will lead with it

Thanks, and congratulations by the library

ofType does no longer automatically infer action type from general source in pipe

After upgrading to from 2.1.4 to 3.2.0 my epics are broken.

Before the update the ofType operator was able to infer the action type for the following operators in the pipe. It seems this no longer works.

This is the error I get after the update:

image

This is the matching action creator:

image

switchMap should be able to infer the type of the action and thus the types of the payload and meta properties.

Edit: I can also confirm that this works in 3.1.0.

Reducer type is any in builds

Describe the bug
Reducer type in createHandlerMap is any.

Expected behavior
createHandlerMap second argument as Reducer should have state and action type.

Environment:

  • Deox version(s): 1.0.1
  • Typescriot version(s): 3.2.2

Possible Solution
convert the types.d.ts file to be an ordinary .ts file which can hold the types in the builds.

Typescript error if trying to combine only one epic ...

First, thanks for this very neat library !

Here's a weird error when used with redux-observable ... if trying to combineEpics only one epic !

I know combineEpics is useless with only one epic ... but if you have 2 epics, and you want to momentary disable one, it shouldn't be a problem IHMO.

I've reproduced the issue based one the example given in the docs : https://deox.js.org/faq#using-redux-observable-with-deox

Here's my CodeSandbox "fork" : https://codesandbox.io/s/redux-observable-example-4g1lt

The only thing I changed from the given one (https://codesandbox.io/s/redux-observable-example-z6sdk) is to replace combineEpics(fetchAllTodosEpic, addTodoEpic) by combineEpics(addTodoEpic) in todos.ts.

The error then pops all the way in create-store.ts on epicMiddleware.run(rootEpic) :

(alias) const rootEpic: (action$: Observable<{
    type: "TODO_ADD_NEXT";
    payload: {
        title: string;
        completed: any;
    };
} | {
    type: "TODO_ADD_ERROR";
} | {
    type: "TODO_ADD_ERROR";
    payload: any;
    error: true;
} | {
    type: "TODO_ADD_ERROR";
    payload: any;
} | {
    ...;
} | {
    ...;
}>, _state$: Observable<...>, { api }: {
    ...;
}) => Observable<...>
import rootEpic
L'argument de type '(action$: Observable<{ type: "TODO_ADD_NEXT"; payload: { title: string; completed: any; }; } | { type: "TODO_ADD_ERROR"; } | { type: "TODO_ADD_ERROR"; payload: any; error: true; } | { type: "TODO_ADD_ERROR"; payload: any; } | { ...; } | { ...; }>, _state$: Observable<...>, { api }: { ...; }) => Observable<...>' n'est pas attribuable au paramètre de type 'Epic<Action<any>, Action<any>, void, { api: typeof import("/sandbox/src/store/api"); }>'.
  Les types des paramètres '_state$' et 'state$' sont incompatibles.
    Impossible d'assigner le type 'StateObservable<void>' au type 'Observable<{ todos: Todo[]; isFetching: boolean; }>'.
      Les types de la propriété 'operator' sont incompatibles.
        Impossible d'assigner le type 'Operator<any, void>' au type 'Operator<any, { todos: Todo[]; isFetching: boolean; }>'.
          Impossible d'assigner le type 'void' au type '{ todos: Todo[]; isFetching: boolean; }'.ts(2345)

Any insight on this ?

An in-range update of rollup is breaking the build 🚨

The devDependency rollup was updated from 1.18.0 to 1.19.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

rollup is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ci/circleci: test: Your tests failed on CircleCI (Details).
  • test-build-release: * test - Failed
  • build - Blocked

Commits

The new version differs by 6 commits.

  • 9af119d 1.19.0
  • b3f361c Update changelog
  • 456f4d2 Avoid variable from empty module name be empty (#3026)
  • 17eaa43 Use id of last module in chunk as name base for auto-generated chunks (#3025)
  • 871bfa0 Switch to a code-splitting build and update dependencies (#3020)
  • 2443783 Unified file emission api (#2999)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Add ActionType helper

It is handy to have a helper that infers action types that a reducer can handle. A use case scenario for this is creating an epic.

Possible Implementation

type ActionType<T extends Reducer<any, AnyAction>> = T extends Reducer<any, infer Actions>  ? Actions : never

Async and dispatch clarification

I don't fully understand how to implement asynchronous actions or dispatch more actions from a handler. Could you clarify this in document?

The automated release is failing 🚨

🚨 The automated release from the 2.1.x branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the 2.1.x branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


The release 3.0.0 on branch 2.1.x cannot be published as it is out of range.

Only releases within the range >=2.1.0 <2.2.0 can be merged into the maintenance branch 2.1.x and published to the 2.1.x distribution channel.

The branch 2.1.x head should be reset to a previous commit so the commit with tag v3.0.0 is removed from the branch history.

See the workflow configuration documentation for more details.


Good luck with your project ✨

Your semantic-release bot 📦🚀

An in-range update of rollup is breaking the build 🚨

The devDependency rollup was updated from 1.9.2 to 1.9.3.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

rollup is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ci/circleci: test: Your tests failed on CircleCI (Details).
  • test-build-release: * test - Failed

Release Notes for v1.9.3

2019-04-10

Bug Fixes

  • Simplify return expressions that are evaluated before the surrounding function is bound (#2803)

Pull Requests

  • #2803: Handle out-of-order binding of identifiers to improve tree-shaking (@lukastaegert)
Commits

The new version differs by 3 commits.

  • 516a06d 1.9.3
  • a5526ea Update changelog
  • c3d73ff Handle out-of-order binding of identifiers to improve tree-shaking (#2803)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Expose createHandlerMap

I am working on a helper function that creates action handlers outside of a createReducer function, and from what I understand I need createHandlerMap to do that. I added my current wip implementation for reference. Maybe there is a different way to do this that I am overlooking.

interface HandleThunkParams<Error, Result, SKey> {
  start: any
  error: any
  complete: any
  cancel: any
  onError?: (err: Error) => {}
  onComplete?: (res: Result) => {}
  statusKey?: SKey
}

export const handleThunk = <Error, Result, SKey extends string>({
  start,
  error,
  complete,
  cancel,
  onError,
  onComplete,
  statusKey,
}: HandleThunkParams<Error, Result, SKey>) => {
  const getStatus = (status: AsyncStatus) => createKeyValue(statusKey || 'status', status)

  return [
    createHandlerMap(start, (state: any) => ({ ...state, ...getStatus(loadingStatus) })),
    createHandlerMap(error, (state: any, action: any) => ({
      ...state,
      ...(onError || identity)(action.payload),
      ...getStatus(errorStatus),
    })),
    createHandlerMap(complete, (state: any, action: any) => ({
      ...state,
      ...(onComplete || identity)(action.payload),
      ...getStatus(readyStatus),
    })),
    createHandlerMap(cancel, (state: any) => ({ ...state, ...getStatus(readyStatus) })),
  ]
}

Add isOfType helper

Use case - a redux saga that is run when two actions dispatched, one with payload, other w/o payload

const fetchEntity = createActionCreator('FETCH', resolve => (id: string) => resolve(id));
const reFetchEntity = createActionCreator('RE_FETCH')

function* watchFetchEntity() {
  yield takeLastest([getType(fetchEntity), getType(reFetchEntity)], fetchEntitySaga);
}

function* fetchEntitySaga(action: ActionType<typeof fetchEntity> | ActionType<typeof reFetchEntity>) {
  const id = isOfType(getType(fetchEntity), action) 
    ? action.payload // id from fetchEntity
    : yield select(selectEntityId)
}

createReducer has wrong type for action arg.

A reducer created with createReducer can be called with any action that has a (string?) type property.
However, createReducer limits its action argument to those that are specifically handled by it.

Due to contravariance, (state: State, action: SpecificActions) => State does not extend (state: State, action: AnyAction) => State.

I know why createReducer is typed in such a way, but I wonder if some conditional type magic can allow us to encode handled action types + any action except those that are handled?

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two-Factor Authentication, make configure the auth-only level is supported. semantic-release cannot publish with the default auth-and-writes level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot 📦🚀

An in-range update of rollup is breaking the build 🚨

The devDependency rollup was updated from 1.14.2 to 1.14.3.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

rollup is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ci/circleci: test: Your tests failed on CircleCI (Details).
  • test-build-release: * test - Failed

Release Notes for v1.14.3

2019-06-06

Bug Fixes

  • Generate correct external imports when importing from a directory that would be above the root of the current working directory (#2902)

Pull Requests

Commits

The new version differs by 4 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Restrict reducer state to be immutable type

Mutation of the state is a common pitfall in flux (especially in beginners).

Detailed Description

Mutation of the state can be prevented by typescript's ReadonlyArray, ReadonlyMap and Readonly types.

Possible Implementation

There is a lib called utility-types that has been implemented DeepReadonly type. Surrounding Reducer type first argument (prevState) and its return type with DeepReadonly can make it possible to implement this feature.

But it looks like there are some issues with DeepReadonly type in the utility-types lib. That's why I think reimplementation of the DeepReadonly type in a better manner would be better than using this lib.
Also, I think naming this type as DeepImmutable is better because it is more declarative and also it can prevent future conflicts with predefined Readonly type.

type Reducer<State, Actions> = (prevState: DeepImmutable<State>, action: Actions) => DeepImmutable<State> | State

State in action handler is inferred as DeepImmutableMap<{}, {}>

Type of state inside callback of handle in createReducer in my app is inferred to be an DeepImmutableMap<{}, {}> although it should be my concrete type. Here is the code I'm using:

// Utility.ts
import { createAction as deoxCreateAction } from "deox";
export const createAction = <T>(type: string) => deoxCreateAction(type, r => (v: T) => r(v));

// Actions.ts
import { CodePreviewActionType } from "~/Store/ActionTypes";
import { createAction } from "~/Utility";

export const showCode = createAction<boolean>(CodePreviewActionType.SHOW_CODE);
export const updateCode = createAction<string>(CodePreviewActionType.UPDATE_CODE);

// index.ts
import { createReducer } from "deox";

import * as CodePreviewActions from "./Actions";

export interface CodePreviewState {
    code: string;
    showCode: boolean;
}

const initialState: CodePreviewState = {
    code: "// Insert your code here\n",
    showCode: true,
};

export const codePreviewReducer = createReducer(initialState, handle => [
    // here state is inferred as DeepImmutableMap<{}, {}> although it should be CodePreviewState 
    handle(CodePreviewActions.showCode, (state , { payload: showCode }) => ({
        ...state,
        showCode,
    })),
    handle(CodePreviewActions.updateCode, (state, { payload: code }) => ({
        ...state,
        code,
    })),
]);

export { CodePreviewActions };

This also causes the resulting codePreviewReducer's type to be wrong.
Example from usage infers the type correctly.

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.