Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better generic feature support #4012

Closed
2 tasks
parloti opened this issue Aug 18, 2023 · 2 comments
Closed
2 tasks

Better generic feature support #4012

parloti opened this issue Aug 18, 2023 · 2 comments

Comments

@parloti
Copy link

parloti commented Aug 18, 2023

Which @ngrx/* package(s) are relevant/related to the feature request?

store

Information

Possibility to create a strongly typed generic feature.

Like that:

export function createAbstractReducer<TState extends IBaseState>(
  initialState: TState
) {
  const reducer = createReducer(
    initialState,
    on(creator, (state: TState) => state) // Error
  );
  return reducer;
}

export function createAbstractFeature<
  TName extends string,
  TState extends IBaseState
>(name: TName, reducer: ActionReducer<TState>) {
  const feature = createFeature({ name, reducer }); // Error

  return feature;
}

Reproduction in the Typescript playground.

I think this is related to typescript not being able to expand conditional generic types.

I've tried doing this Issue with generic properties when type mapping and that How to test if two types are exactly the same but I haven't been able to get it to work.

This was already provided in #2982, but it's back.

Describe any alternatives/workarounds you're currently using

Casting the state to a concrete type.

Like that:

export function createConcretReducer<TState extends IBaseState>(
  initialState: TState
) {
  const reducer = createReducer(
    initialState as IBaseState,
    on(creator, (state: IBaseState) => state) // OK
  );
  return reducer;
}

I would be willing to submit a PR to fix this issue

  • Yes
  • No
@timdeschryver
Copy link
Member

A workaround is to let the state infer it's type:

export function createAbstractReducer<TState extends IBaseState>(
  initialState: TState
) {
  const reducer = createReducer(
    initialState,
    //            👇
    on(creator, (state) => state)
  );
  return reducer;
}

I prefer to leave it like this because the types for this are already quite complex.

@michaelurban
Copy link

michaelurban commented May 27, 2024

@timdeschryver What do you do when you need initialState to be TState | null? The types for this are very complex and I'm not sure what I'm missing. I've tried everything I can think of and the only thing I've gotten to work is to call createReducer with createReducer<any>([...])

  function createAbstractReducer<
    TState extends {
      isActive: boolean;
    },
  >(initialState: TState | null) {
    const reducer = createReducer(
      initialState,
      on(actions.verificationRequest, () => ({
        isActive: true,
      })),
      on(actions.reset, (state) => state),
    );
    return reducer;
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants