conorhastings / use-reducer-with-side-effects

MIT License
89 stars 18 forks source link

Typings #36

Open kennetpostigo opened 4 years ago

kennetpostigo commented 4 years ago

Is there anyway we can get some typescript definitions for this library?

jamesplease commented 4 years ago

iirc @chrisdhanaraj has some he may be able to share

chrisdhanaraj commented 4 years ago

Yes! I'll open a PR tomorrow to definitely typed and cc you both

kennetpostigo commented 4 years ago

@chrisdhanaraj any chance I can get a copy of your types to add to my local type defs?

jamesplease commented 4 years ago

I found the link he sent to me with the typings, but I’ll need to wait til tomorrow to post em here as I’m not on my work computer.

jamesplease commented 4 years ago

These may not be for the latest version of the lib, but here's what Chris sent me a few months back:

/* eslint-disable @typescript-eslint/no-explicit-any */

declare module 'use-reducer-with-side-effects' {
  import { Dispatch } from 'react';
  // useCreateReducerWithEffect has a different signature than the original reducer
  // so need to redeclare each of these properties

  type Reducer<S, A> = (prevState: S, action: A) => ReducerReturn<S, A> | symbol;
  type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
  type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;

  export type ReducerReturnBeta<S, A> =
    | UpdateReturn<S>
    | symbol
    | SideEffectReturn<S, A>
    | UpdateWithSideEffectReturn<S, A>;

  export interface ReducerReturn<S, A> {
    newState?: S;
    newSideEffect?: SideEffectCallback<S, A>;
  }

  export type SideEffectCallback<S, A> = (state: S, dispatch: Dispatch<A>) => void;

  export interface UpdateReturn<S> {
    newState: S;
  }

  export function Update<S>(newState: S): UpdateReturn<S>;

  export function NoUpdate(): symbol;

  export interface SideEffectReturn<S, A> {
    newSideEffect: SideEffectCallback<S, A>;
  }
  export function SideEffect<S, A>(state: S, dispatch: Dispatch<A>): SideEffectReturn<S, A>;

  export interface UpdateWithSideEffectReturn<S, A> {
    newState: S;
    newSideEffect: SideEffectCallback<S, A>;
  }

  export interface DefaultInitState<I> {
    sideEffects: [];
    state: I;
  }

  export function UpdateWithSideEffect<S, A>(
    newState: S,
    newSideEffect: SideEffectCallback<S, A>
  ): UpdateWithSideEffectReturn<S, A>;

  export default function useCreateReducerWithEffect<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer?: undefined
  ): [ReducerState<R>, Dispatch<ReducerAction<R>>];

  export default function useCreateReducerWithEffect<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (init: DefaultInitState<I>) => DefaultInitState<I>
  ): [ReducerState<R>, Dispatch<ReducerAction<R>>];
}
chrisdhanaraj commented 4 years ago

Ah sorry! These are actually very bad - I'll be out of work and will send these in an hour

On Fri, Mar 13, 2020 at 2:57 PM, James < notifications@github.com > wrote:

These may not be for the latest version of the lib, but here's what Chris sent me a few months back:

/ eslint-disable @typescript-eslint/no-explicit-any / declare module ' use-reducer-with-side-effects ' { import { Dispatch } from ' react ' ; // useCreateReducerWithEffect has a different signature than the original reducer // so need to redeclare each of these properties type Reducer < S , A > = ( prevState : S , action : A ) => ReducerReturn < S , A > | symbol ; type ReducerState < R extends Reducer < any , any >> = R extends Reducer < infer S , any > ? S : never ; type ReducerAction < R extends Reducer < any , any >> = R extends Reducer < any , infer A > ? A : never ; export type ReducerReturnBeta < S , A > = | UpdateReturn < S > | symbol | SideEffectReturn < S , A > | UpdateWithSideEffectReturn < S , A

; export interface ReducerReturn < S , A > { newState ? : S ; newSideEffect ? : SideEffectCallback < S , A >; } export type SideEffectCallback < S , A > = ( state : S , dispatch : Dispatch < A >) => void ; export interface UpdateReturn < S > { newState : S ; } export function Update< S >( newState : S ) : UpdateReturn < S >; export function NoUpdate() : symbol ; export interface SideEffectReturn < S , A > { newSideEffect : SideEffectCallback < S , A >; } export function SideEffect< S , A >( state : S , dispatch : Dispatch < A ) : SideEffectReturn < S , A >; export interface UpdateWithSideEffectReturn < S , A > { newState : S ; newSideEffect : SideEffectCallback < S , A >; } export interface DefaultInitState < I > { sideEffects : []; state : I ; } export function UpdateWithSideEffect< S , A >( newState : S , newSideEffect : SideEffectCallback < S , A > ) : UpdateWithSideEffectReturn < S , A >; export default function useCreateReducerWithEffect< R extends Reducer < any , any >, I >( reducer : R , initializerArg : I , initializer ? : undefined ) : [ ReducerState < R >, Dispatch < ReducerAction < R >>]; export default function useCreateReducerWithEffect< R extends Reducer < any , any >, I >( reducer : R , initializerArg : I , initializer : ( init : DefaultInitState < I >) => DefaultInitState < I > ) : [ ReducerState < R >, Dispatch < ReducerAction < R >>]; }

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub ( https://github.com/conorhastings/use-reducer-with-side-effects/issues/36#issuecomment-598933320 ) , or unsubscribe ( https://github.com/notifications/unsubscribe-auth/AAJVCXUSSLQWMSXAHB5AN7LRHKT4VANCNFSM4LD5SPBA ).

kennetpostigo commented 4 years ago

@jamesplease Thank you! Will use those for now! 🙏

chrisdhanaraj commented 4 years ago

@kennetpostigo I AM SO SORRY, oh my god, try these on

declare module "use-reducer-with-side-effects" {
  export type ReducerReturn<ReducerState, ReducerActions> =
    | symbol
    | UpdateReturn<ReducerState>
    | SideEffectReturn<ReducerState, ReducerActions>
    | UpdateWithSideEffectReturn<ReducerState, ReducerActions>;

  export type Reducer<ReducerState, ReducerActions> = (
    state: ReducerState,
    action: ReducerActions
  ) => ReducerReturn<ReducerState, ReducerActions>;

  export type Dispatch<ReducerActions> = (action: ReducerActions) => void;

  export interface StateShape<ReducerState> {
    sideEffects: any[];
    state: ReducerState;
  }

  export type CancelFunction = () => void;
  export type SideEffectCallback<ReducerState, ReducerActions> = (
    state: ReducerState,
    dispatch: Dispatch<ReducerActions>
  ) => void | CancelFunction;

  export function NoUpdate(): symbol;

  export interface SideEffectReturn<ReducerState, ReducerActions> {
    sideEffects: SideEffectCallback<ReducerState, ReducerActions>[];
  }

  export function SideEffect<ReducerState, ReducerActions>(
    args: SideEffectCallback<ReducerState, ReducerActions>
  ): SideEffectReturn<ReducerState, ReducerActions>;

  export interface UpdateReturn<ReducerState> {
    state: ReducerState;
  }

  export function Update<ReducerState>(
    state: ReducerState
  ): UpdateReturn<ReducerState>;

  export interface UpdateWithSideEffectReturn<ReducerState, ReducerActions> {
    state: ReducerState;
    sideEffects: SideEffectCallback<ReducerState, ReducerActions>[];
  }

  export function UpdateWithSideEffect<ReducerState, ReducerActions>(
    state: any,
    sideEffects: SideEffectCallback<ReducerState, ReducerActions>
  ): UpdateWithSideEffectReturn<ReducerState, ReducerActions>;

  export default function useCreateReducerWithEffect<
    ReducerState,
    ReducerActions
  >(
    reducer: Reducer<ReducerState, ReducerActions>,
    initializerArg: ReducerState,
    initializer?: (args: StateShape<ReducerState>) => StateShape<ReducerState>
  ): [ReducerState, Dispatch<ReducerActions>];
}
kennetpostigo commented 4 years ago

Thank you @chrisdhanaraj!

szg251 commented 4 years ago

I added type definitions to the library itself in a PR and I currently use the generated definitions in one of my projects:

import { Dispatch } from "react"

declare module "use-reducer-with-side-effects" {
  export declare type ReducerWithSideEffects<S, A> = (
    prevState: S,
    action: A | NoUpdateSymbol
  ) => Partial<StateWithSideEffects<S, A>> | NoUpdateSymbol
  export declare type StateWithSideEffects<S, A> = {
    state: S
    sideEffects: SideEffect<S, A>[]
  }
  export declare type SideEffect<S, A> = (
    state: S,
    dispatch: Dispatch<A>
  ) => Promise<void> | void | CancelFunc<S>
  export declare type CancelFunc<S> = (state: S) => void
  export declare type NoUpdateSymbol = typeof NO_UPDATE_SYMBOL
  export declare const NO_UPDATE_SYMBOL: unique symbol
  export declare const Update: <S>(
    state: S
  ) => {
    state: S
  }
  export declare const NoUpdate: () => typeof NO_UPDATE_SYMBOL
  export declare const UpdateWithSideEffect: <S, A>(
    state: S,
    sideEffects: SideEffect<S, A>[]
  ) => {
    state: S
    sideEffects: SideEffect<S, A>[]
  }
  export declare const SideEffect: <S, A>(
    sideEffects: SideEffect<S, A>[]
  ) => {
    sideEffects: SideEffect<S, A>[]
  }
  export declare function executeSideEffects<S, A>({
    sideEffects,
    state,
    dispatch,
  }: {
    sideEffects: SideEffect<S, A>[]
    state: S
    dispatch: Dispatch<A>
  }): Promise<CancelFunc<S>[]>
  export declare function mergeState<S, A>(
    prevState: StateWithSideEffects<S, A>,
    newState: Partial<StateWithSideEffects<S, A>> | NoUpdateSymbol,
    isUpdate: boolean
  ): StateWithSideEffects<S, A>
  export default function useCreateReducerWithEffect<S, A>(
    reducer: ReducerWithSideEffects<S, A>,
    initialState: S,
    init?: (state: S) => Partial<StateWithSideEffects<S, A>>
  ): [S, Dispatch<A | NoUpdateSymbol>]
  export declare function composeReducers<S, A>(
    reducers: ReducerWithSideEffects<S, A>[]
  ): (
    state: S,
    action: A
  ) =>
    | typeof NO_UPDATE_SYMBOL
    | {
        state: S | undefined
        sideEffects: SideEffect<S, A>[]
      }
}
stuckj commented 3 years ago

FYI, typings from @gege251 and @chrisdhanaraj have a minor difference from @conorhastings's implementation. In the UpdateWithSideEffect function, the sideEffects parameter may be EITHER a function OR an array of functions. This applies to the SideEffect function as well. You can see this in the mergeState function in the library (this line).

Here are updated types for UpdateWithSideEffect and SideEffect that handle both cases.

  export function SideEffect<ReducerState, ReducerActions>(
    args:
      | SideEffectCallback<ReducerState, ReducerActions>[]
      | SideEffectCallback<ReducerState, ReducerActions>
  ): SideEffectReturn<ReducerState, ReducerActions>

  export function UpdateWithSideEffect<ReducerState, ReducerActions>(
    state: any,
    sideEffects:
      | SideEffectCallback<ReducerState, ReducerActions>[]
      | SideEffectCallback<ReducerState, ReducerActions>
  ): UpdateWithSideEffectReturn<ReducerState, ReducerActions>

It's the same idea for @gege251's types but with the shorter generic parameters.

As an aside. Is there any problem with just adding a use-reducer-with-side-effects.d.ts file into the library without typescript support (as @gege251 had added in his PR) to make this easier for everyone using typescript? I'm fairly new to typescript so I didn't know if just the presence of the type definitions in the library was sufficient (it seems to be in the individual project). That seems like a far less obtrusive change that I'm assuming @conorhastings would be ok with pulling in since it literally doesn't change any existing code.

If anyone more familiar can chime in as to whether that's enough for adding typing then I'm happy to throw what I've got working from my project into a .d.ts file and make a PR.

stuckj commented 3 years ago

Made a PR for this here: https://github.com/conorhastings/use-reducer-with-side-effects/pull/45. I believe just using index.d.ts should work from what I read up about this on typescript. The alternative to putting this in the library is to submit it to DefinitelyTypes which should also work as long as you add @types/node to your project (if doing a node project).