diff --git a/.eslintrc.yaml b/.eslintrc.yaml index d96b427..b23064f 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -1,7 +1,11 @@ extends: - eslint:recommended + - plugin:react/recommended - plugin:@typescript-eslint/eslint-recommended - plugin:@typescript-eslint/recommended +settings: + react: + version: 'detect' rules: max-len: - warn diff --git a/package-lock.json b/package-lock.json index a16f842..eceb0d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3059,6 +3059,16 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", @@ -6455,6 +6465,54 @@ } } }, + "eslint-plugin-react": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", + "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.1.0", + "object.entries": "^1.1.0", + "object.fromentries": "^2.0.0", + "object.values": "^1.1.0", + "prop-types": "^15.7.2", + "resolve": "^1.10.1" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "eslint-scope": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", @@ -10112,6 +10170,16 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", + "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "object.assign": "^4.1.0" + } + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -11700,6 +11768,42 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -11719,6 +11823,18 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "octokit-pagination-methods": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", diff --git a/package.json b/package.json index 98b5993..f6ed2d7 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "common-shakeify": "^0.6.0", "cookie-parser": "^1.4.4", "deep-object-diff": "^1.1.0", + "eslint-plugin-react": "^7.14.3", "esmify": "git+https://github.com/jeremija/esmify.git", "formik": "^1.5.8", "history": "^4.9.0", diff --git a/packages/redux/src/actions/Action.ts b/packages/redux/src/actions/Action.ts new file mode 100644 index 0000000..358c2b6 --- /dev/null +++ b/packages/redux/src/actions/Action.ts @@ -0,0 +1,4 @@ +export interface Action { + payload: T + type: ActionType +} diff --git a/packages/redux/src/actions/AsyncAction.ts b/packages/redux/src/actions/AsyncAction.ts new file mode 100644 index 0000000..aff411d --- /dev/null +++ b/packages/redux/src/actions/AsyncAction.ts @@ -0,0 +1,10 @@ +import {PendingAction} from './PendingAction' +import {ResolvedAction} from './ResolvedAction' +import {RejectedAction} from './RejectedAction' + +export type TAsyncStatus = 'pending' | 'resolved' | 'rejected' + +export type AsyncAction = + PendingAction + | ResolvedAction + | RejectedAction diff --git a/packages/redux/src/actions/TGetAction.ts b/packages/redux/src/actions/GetAction.ts similarity index 52% rename from packages/redux/src/actions/TGetAction.ts rename to packages/redux/src/actions/GetAction.ts index 31fb353..a151559 100644 --- a/packages/redux/src/actions/TGetAction.ts +++ b/packages/redux/src/actions/GetAction.ts @@ -1,6 +1,6 @@ -import {IAction} from './IAction' +import {Action} from './Action' export type TGetAction = - ActionTypes extends IAction + ActionTypes extends Action ? ActionTypes : never diff --git a/packages/redux/src/actions/GetPendingAction.ts b/packages/redux/src/actions/GetPendingAction.ts new file mode 100644 index 0000000..ad1dabe --- /dev/null +++ b/packages/redux/src/actions/GetPendingAction.ts @@ -0,0 +1,6 @@ +import {AsyncAction} from './AsyncAction' + +export type GetPendingAction = + MyTypes extends AsyncAction & {status: 'pending'} + ? MyTypes + : never diff --git a/packages/redux/src/actions/GetResolvedAction.ts b/packages/redux/src/actions/GetResolvedAction.ts new file mode 100644 index 0000000..b43640b --- /dev/null +++ b/packages/redux/src/actions/GetResolvedAction.ts @@ -0,0 +1,6 @@ +import {AsyncAction} from './AsyncAction' + +export type GetResolvedAction = + MyTypes extends AsyncAction & {status: 'resolved'} + ? MyTypes + : never diff --git a/packages/redux/src/actions/IAction.ts b/packages/redux/src/actions/IAction.ts deleted file mode 100644 index 5a0b3d2..0000000 --- a/packages/redux/src/actions/IAction.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IAction { - payload: T - type: ActionType -} diff --git a/packages/redux/src/actions/IPendingAction.ts b/packages/redux/src/actions/IPendingAction.ts deleted file mode 100644 index 0bb5ade..0000000 --- a/packages/redux/src/actions/IPendingAction.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IAction} from './IAction' - -export interface IPendingAction extends - IAction, ActionType> { - status: 'pending' -} diff --git a/packages/redux/src/actions/IRejectedAction.ts b/packages/redux/src/actions/IRejectedAction.ts deleted file mode 100644 index 199e129..0000000 --- a/packages/redux/src/actions/IRejectedAction.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {IAction} from './IAction' - -export interface IRejectedAction extends - IAction { - status: 'rejected' -} diff --git a/packages/redux/src/actions/IResolvedAction.ts b/packages/redux/src/actions/IResolvedAction.ts deleted file mode 100644 index d92c90f..0000000 --- a/packages/redux/src/actions/IResolvedAction.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {IAction} from './IAction' - -export interface IResolvedAction extends - IAction { - payload: T - status: 'resolved' -} diff --git a/packages/redux/src/actions/PendingAction.ts b/packages/redux/src/actions/PendingAction.ts index 34ff048..3f7cac1 100644 --- a/packages/redux/src/actions/PendingAction.ts +++ b/packages/redux/src/actions/PendingAction.ts @@ -1,9 +1,6 @@ -import {IPendingAction} from './IPendingAction' +import {Action} from './Action' -export class PendingAction { - readonly status = 'pending' - constructor( - readonly payload: T, - readonly type: ActionType, - ) {} +export interface PendingAction extends + Action, ActionType> { + status: 'pending' } diff --git a/packages/redux/src/actions/RejectedAction.ts b/packages/redux/src/actions/RejectedAction.ts new file mode 100644 index 0000000..3456f65 --- /dev/null +++ b/packages/redux/src/actions/RejectedAction.ts @@ -0,0 +1,6 @@ +import {Action} from './Action' + +export interface RejectedAction extends + Action { + status: 'rejected' +} diff --git a/packages/redux/src/actions/ResolvedAction.ts b/packages/redux/src/actions/ResolvedAction.ts new file mode 100644 index 0000000..deb9529 --- /dev/null +++ b/packages/redux/src/actions/ResolvedAction.ts @@ -0,0 +1,7 @@ +import {Action} from './Action' + +export interface ResolvedAction extends + Action { + payload: T + status: 'resolved' +} diff --git a/packages/redux/src/actions/TAsyncAction.ts b/packages/redux/src/actions/TAsyncAction.ts deleted file mode 100644 index 2c4fea1..0000000 --- a/packages/redux/src/actions/TAsyncAction.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {IPendingAction} from './IPendingAction' -import {IResolvedAction} from './IResolvedAction' -import {IRejectedAction} from './IRejectedAction' - -export type TAsyncStatus = 'pending' | 'resolved' | 'rejected' - -export type TAsyncAction = - IPendingAction - | IResolvedAction - | IRejectedAction diff --git a/packages/redux/src/actions/TGetPendingAction.ts b/packages/redux/src/actions/TGetPendingAction.ts deleted file mode 100644 index 5e5ac5c..0000000 --- a/packages/redux/src/actions/TGetPendingAction.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {TAsyncAction} from './TAsyncAction' - -export type TGetPendingAction = - MyTypes extends TAsyncAction & {status: 'pending'} - ? MyTypes - : never diff --git a/packages/redux/src/actions/TGetResolvedAction.ts b/packages/redux/src/actions/TGetResolvedAction.ts deleted file mode 100644 index 9cb3e70..0000000 --- a/packages/redux/src/actions/TGetResolvedAction.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {TAsyncAction} from './TAsyncAction' - -export type TGetResolvedAction = - MyTypes extends TAsyncAction & {status: 'resolved'} - ? MyTypes - : never diff --git a/packages/redux/src/actions/createPendingAction.ts b/packages/redux/src/actions/createPendingAction.ts new file mode 100644 index 0000000..534a229 --- /dev/null +++ b/packages/redux/src/actions/createPendingAction.ts @@ -0,0 +1,8 @@ +import {PendingAction} from './IPendingAction' + +export function createPendingAction( + payload: Promise, + type: ActionType, +): PendingAction { + return {payload, type, status: 'pending'} +} diff --git a/packages/redux/src/actions/index.ts b/packages/redux/src/actions/index.ts index ff3d921..caed81b 100644 --- a/packages/redux/src/actions/index.ts +++ b/packages/redux/src/actions/index.ts @@ -1,9 +1,9 @@ -export * from './IAction' -export * from './IPendingAction' -export * from './IRejectedAction' -export * from './IResolvedAction' +export * from './Action' +export * from './createPendingAction' +export * from './RejectedAction' +export * from './ResolvedAction' export * from './PendingAction' -export * from './TAsyncAction' -export * from './TGetAction' -export * from './TGetPendingAction' -export * from './TGetResolvedAction' +export * from './AsyncAction' +export * from './GetAction' +export * from './GetPendingAction' +export * from './GetResolvedAction' diff --git a/packages/redux/src/bindActionCreators.ts b/packages/redux/src/bindActionCreators.ts index cf69bf5..e43f8af 100644 --- a/packages/redux/src/bindActionCreators.ts +++ b/packages/redux/src/bindActionCreators.ts @@ -4,5 +4,5 @@ export function bindActionCreators( obj: T, dispatch: Dispatch, ): T { - return bind(obj as any, dispatch) + return bind(obj as any, dispatch) // eslint-disable-line } diff --git a/packages/redux/src/middleware/PromiseMiddleware.ts b/packages/redux/src/middleware/PromiseMiddleware.ts index 1e6e45e..e14dc98 100644 --- a/packages/redux/src/middleware/PromiseMiddleware.ts +++ b/packages/redux/src/middleware/PromiseMiddleware.ts @@ -1,9 +1,8 @@ -import assert from 'assert' import {AnyAction, Middleware} from 'redux' -function isPromise(value: any): value is Promise { +function isPromise(value: unknown): value is Promise { return value && typeof value === 'object' && - typeof (value as any).then === 'function' + typeof (value as Promise).then === 'function' } /** @@ -21,7 +20,7 @@ function isPromise(value: any): value is Promise { */ export class PromiseMiddleware { handle: Middleware = store => next => (action: AnyAction) => { - const {payload, type} = action + const {payload} = action if (!isPromise(payload)) { return next(action) } diff --git a/packages/redux/src/middleware/WaitMiddleware.test.ts b/packages/redux/src/middleware/WaitMiddleware.test.ts index 460e775..f3585f6 100644 --- a/packages/redux/src/middleware/WaitMiddleware.test.ts +++ b/packages/redux/src/middleware/WaitMiddleware.test.ts @@ -1,14 +1,12 @@ -import {WaitMiddleware} from './WaitMiddleware' -import { - IAction, IPendingAction, IResolvedAction, IRejectedAction, -} from '../actions' -import {applyMiddleware, createStore, AnyAction} from 'redux' import { getError } from '@rondo.dev/test-utils' +import { applyMiddleware, createStore } from 'redux' +import { Action } from '../actions' +import { WaitMiddleware } from './WaitMiddleware' describe('WaitMiddleware', () => { const getStore = (wm: WaitMiddleware) => createStore( - (state: string[] = [], action: IAction) => { + (state: string[] = [], action: Action) => { return [...state, action.type] }, [], diff --git a/packages/redux/src/middleware/WaitMiddleware.ts b/packages/redux/src/middleware/WaitMiddleware.ts index f3406fb..831edd8 100644 --- a/packages/redux/src/middleware/WaitMiddleware.ts +++ b/packages/redux/src/middleware/WaitMiddleware.ts @@ -1,15 +1,15 @@ -import {TAsyncAction} from '../actions/TAsyncAction' +import {AsyncAction} from '../actions/AsyncAction' import {AnyAction, Middleware} from 'redux' export class WaitMiddleware { - protected notify?: (action: TAsyncAction) => void + protected notify?: (action: AsyncAction) => void protected recorders: Recorder[] = [] - handle: Middleware = store => next => (action: AnyAction) => { + handle: Middleware = () => next => (action: AnyAction) => { next(action) this.recorders.forEach(recorder => recorder.record(action)) if (this.notify && 'status' in action) { - this.notify(action as TAsyncAction) + this.notify(action as AsyncAction) } } @@ -66,7 +66,7 @@ export class WaitMiddleware { this.notify = undefined }, timeout) - this.notify = (action: TAsyncAction) => { + this.notify = (action: AsyncAction) => { if (!actionsByName[action.type]) { return } @@ -77,11 +77,13 @@ export class WaitMiddleware { actionsByName[action.type]-- count-- if (count === 0) { + clearTimeout(t) resolve() this.notify = undefined } return case 'rejected': + clearTimeout(t) reject(action.payload) this.notify = undefined return diff --git a/packages/redux/src/pack.test.tsx b/packages/redux/src/pack.test.tsx index 41dd7e3..707d72d 100644 --- a/packages/redux/src/pack.test.tsx +++ b/packages/redux/src/pack.test.tsx @@ -7,19 +7,19 @@ import { pack, TStateSelector } from './pack' describe('pack', () => { - interface IChangeAction { - payload: {a: number, b: string}, + interface ChangeAction { + payload: {a: number, b: string} type: 'CHANGE' } - interface IProps { + interface Props { a: number b: string - update(a: number, b: string): IChangeAction + update(a: number, b: string): ChangeAction c: string[] } - class PureComponent extends React.PureComponent { + class PureComponent extends React.PureComponent { update = () => { this.props.update(1, 'one') } @@ -34,7 +34,7 @@ describe('pack', () => { } } - function FunctionalComponent(props: IProps) { + function FunctionalComponent(props: Props) { const update = useCallback(() => props.update(1, 'one'), []) return ( @@ -44,15 +44,15 @@ describe('pack', () => { ) } - type LocalState = Omit - interface IState { + type LocalState = Omit + interface State { localState: LocalState } function reduce( - state: IState = {localState: {a: 0, b: ''}}, + state: State = {localState: {a: 0, b: ''}}, action: any, - ): IState { + ): State { switch (action.type) { case 'CHANGE': return { @@ -73,7 +73,7 @@ describe('pack', () => { getLocalState, (localState: LocalState) => localState, { - update(a: number, b: string): IChangeAction { + update(a: number, b: string): ChangeAction { return { payload: {a, b}, type: 'CHANGE', @@ -91,7 +91,7 @@ describe('pack', () => { getLocalState, (localState: LocalState) => localState, { - update(a: number, b: string): IChangeAction { + update(a: number, b: string): ChangeAction { return { payload: {a, b}, type: 'CHANGE', @@ -102,10 +102,10 @@ describe('pack', () => { ) } - const PackedPureComponent = configurePureComponent( + const PackedPureComponent = configurePureComponent( state => state.localState) - const PackedFunctionalComponent = configureFunctionalComponent( + const PackedFunctionalComponent = configureFunctionalComponent( state => state.localState) it('creates a connected component', () => { diff --git a/packages/redux/src/pack.ts b/packages/redux/src/pack.ts index e7251d4..26b6968 100644 --- a/packages/redux/src/pack.ts +++ b/packages/redux/src/pack.ts @@ -1,6 +1,4 @@ -import { ComponentType, PureComponent } from 'react' -import { connect, Omit, MapDispatchToPropsParam, Matching, GetProps, ResolveThunks } from 'react-redux' -import { Dispatch } from 'redux' +import { connect, GetProps, MapDispatchToPropsParam, Matching, ResolveThunks } from 'react-redux' /* * Select and return a part of the state diff --git a/packages/redux/src/store/createStore.ts b/packages/redux/src/store/createStore.ts index fcf3a96..d828488 100644 --- a/packages/redux/src/store/createStore.ts +++ b/packages/redux/src/store/createStore.ts @@ -1,14 +1,7 @@ -import {ReduxLogger, PromiseMiddleware, WaitMiddleware} from '../middleware' -import { - applyMiddleware, - createStore as create, - Middleware, - Action, - DeepPartial, - Reducer, -} from 'redux' +import { Action, applyMiddleware, createStore as create, DeepPartial, Middleware, Reducer } from 'redux' +import { PromiseMiddleware, ReduxLogger } from '../middleware' -export interface ICreateStoreParams { +export interface CreateStoreParams { reducer: Reducer state?: Partial middleware?: Middleware[] @@ -19,7 +12,7 @@ export interface ICreateStoreParams { * Create a Redux store. */ export function createStore( - params: ICreateStoreParams, + params: CreateStoreParams, ) { const middleware = params.middleware || [ new ReduxLogger(