conorhastings/use-reducer-with-side-effects

Typings

Opened this issue · 12 comments

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

iirc @chrisdhanaraj has some he may be able to share

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

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

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.

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>>];
}

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

@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>];
}

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>[]
      }
}

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.

Made a PR for this here: #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).