the-dr-lazy / deox Goto Github PK
View Code? Open in Web Editor NEWFunctional Type-safe Flux Standard Utilities
Home Page: https://deox.js.org
License: MIT License
Functional Type-safe Flux Standard Utilities
Home Page: https://deox.js.org
License: MIT License
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:
This is the matching action creator:
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.
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 ?
1.9.2
to 1.9.3
.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.
2019-04-10
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
There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.
Your Greenkeeper Bot 🌴
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.
According to #2, createHandlerMap
should be capable to handle optimistic action creators.
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.
createHandlerMap(addFoo, {
next(state, action) {},
error(state, action) {},
complete(state, action) {},
cancel(state, action) {}
})
Mutation of the state is a common pitfall in flux (especially in beginners).
Mutation of the state can be prevented by typescript's ReadonlyArray
, ReadonlyMap
and Readonly
types.
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
As we are using docz for documentation site with .mdx files. It's important to add this file extension to the lint-stage to run prettier on them.
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?
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.
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
.
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)
}
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.
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
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!
Any idea how to get rid of error Type 'DeepImmutableArray<string>' is missing the following properties from type...
except import DeepImmutable
from 'deox/dist/types' or explicty declare props?
Demo: https://codesandbox.io/s/vyqkr8y643?fontsize=14
Error in Component.tsx
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.
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)
}
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?
As in topic. Why? Is that necessary?
PS. Really helpful lib, keep coding :)
According to #17 and #32.
It is important to have a documented example of using deox with redux-thunk.
const myAction = createAction('MY_ACTION');
myAction('woot', 14, null, undefined); // Does not complain
This is doing the same thing as far as I can see, but it complains correctly:
export const myAction = createAction('MY_ACTION', (resolve) => () => resolve());
myAction('woot', 14, null, undefined); // Error
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.
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 📦🚀
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
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?
devDependency
@commitlint/cli was updated from 8.0.0
to 8.1.0
.devDependency
@commitlint/config-conventional was updated from 8.0.0
to 8.1.0
.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.
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
There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.
Your Greenkeeper Bot 🌴
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.
The documentation site menu titles should be linked in readme in the documentation section as an unordered list.
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.
The documentation should explain the following topics:
examples
directory at the root of the repoDeox needs a good documentation site for better SEO and DX.
I found docz as a good documentation site generator that has almost no cost.
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) })),
]
}
1.14.2
to 1.14.3
.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.
2019-06-06
The new version differs by 4 commits.
c68bd95
1.14.3
d79aa57
Update changelog
7179390
Use browser relative path algorithm for chunks (#2902)
b1df517
Add funding button
See the full diff
There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.
Your Greenkeeper Bot 🌴
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.
According to js-org/js.org#2920
The deox.js.org is now available. So now it's possible to use the main domain in the readme links to the documentation site.
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
})),
]);
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.
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 📦🚀
1.18.0
to 1.19.0
.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.
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
There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.
Your Greenkeeper Bot 🌴
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.
For ReadonlyArray
:
type A = DeepImmutable<DeepImmutable<string[]>>
For ReadonlyMap
:
type B = DeepImmutable<DeepImmutable<Map<'key', 'value'>>>
For ReadonlyArray
:
type A = DeepImmutableArray<string>
For ReadonlyMap
:
type B = DeepImmutableMap<'key', 'value'>
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<...>;
}
Add conditions to check ReadonlyArray
and ReadonlyMap
in DeepImmutable
and Immutable
types.
API reference should cover the public API of the library.
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
Describe the bug
Reducer
type in createHandlerMap
is any.
Expected behavior
createHandlerMap
second argument as Reducer
should have state and action type.
Environment:
Possible Solution
convert the types.d.ts file to be an ordinary .ts file which can hold the types in the builds.
I don't fully understand how to implement asynchronous actions or dispatch more actions from a handler. Could you clarify this in document?
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.
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
Let's use this thread to discuss actual design considerations for a plugin system API.
Prior references:
action creator creation for optimistic flow is a good point to reduce boilerplate with a factory function.
Each typical optimistic flow has 4 potential types of action:
According to the nature of action types, all of these actions maybe contains payload and/or meta.
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.
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.
type ActionType<T extends Reducer<any, AnyAction>> = T extends Reducer<any, infer Actions> ? Actions : never
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.
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.
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.
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.