1

I'm having a bit of trouble figuring out how to solve my problem, despite the many answers I've already see on similar subjects.

I have the following types -- I'll add more later (one for each form roughly):

export interface FormError {
  formError: string
}

export interface InputState<T> {
  value: T
  onError: boolean
  validation: FormDataValidation[] | undefined
  errorMessage?: string
}

export interface WorkoutForm extends FormError {
  trainingType: InputState<trainingType>
  name: InputState<string>
  date: InputState<string>
}

export interface LoginForm extends FormError {
  email: InputState<string>
  password: InputState<string>
}

I have the following function where I'd like to be able to use a "generic" type so that I can use it with WorkoutForm or with LoginForm or with others that I'll create later.

I've tried to solve my problem with T but my linter gives me lots of errors every time.

public validate<T>(formData: T, index: Exclude<keyof T, 'formError'>): T {
    const validation = formData[index].validation; // << Property 'validation' does not exist on type 'T[Exclude ]'
    // ....... code
    return formData;
  }

I'd like to be able to use it like this, for example:

function handleFormData(index: Exclude<keyof LoginForm, 'formError'>, value: string): void {
  // ... some code
  const currentFormData = formData; // currentFormData type is LoginForm
  currentFormData[index].value = value;
  const validatedData = FormValidation.getInstance().validate<LoginForm>(currentFormData, index);
  // ... some code
}

Any tips or idea to solve this problem?

Hadock
  • 796
  • 1
  • 12
  • 28
  • 1
    Does [this approach](https://tsplay.dev/mqpkYN) meet your needs? If so I'll write up an answer explaining; if not, what am I missing? – jcalz Jun 29 '23 at 14:48
  • @jcalz yeah it's look pretty ok :) – Hadock Jul 10 '23 at 09:47
  • I used `unknown` in place of the `any` for avoiding another error and forced me to check the type, but everything working great; thanks mate ! – Hadock Jul 10 '23 at 10:17

1 Answers1

1

You should make validate() generic in both T, the type of formData, as well as K, the type of index. Additionally, you should constrain T and K so that K is a keylike type, and T is known to be an object with a property whose key type is K and whose value type is some InputState<any> (you can use unknown instead of any as long as InputState<T> is covariant in T, see Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript for more information). That can be written as Record<K, InputState<any>> using the Record<K, V> utility type. Like so:

function validate<
  T extends Record<K, InputState<any>>,
  K extends PropertyKey>(
    formData: T, index: K): T {
  const validation = formData[index].validation;
  return formData;
}

That compiles with no error, and now the rest of your code behaves as desired.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360