How to force override an existing type in Typescript?

Viewed 778

I am using the Formik, a React form library and creating my own custom fields. I'm relying on Formik's generic Schema type which represents their values object, which contains all the values for all the fields in the form.

The custom component is checkboxgroup component that takes FormikProps

type FormikProps<Schema> = {
  ...
  values: Schema
}

type Groceries = Array<{
  id: string;
  value: string;
}>

type Schema = {
  ...
  firstName: string;
  lastName: string;
  groceries: Groceries
}

type Props<Schema> = {
  ...
  name: keyof Schema & string;
} & FormikProps<Schema, unknown>

type State = {
  isBananaSelectedInGroceries: boolean;
}

const getInitialState = (props: Props) => {
  const groceries = props.values["groceries"];

  // Property 'map' does not exist on type 'Schema[keyof Schema & string]'.ts(2339)
  const values = groceries.map(({ value }) => value);
  ...
}

export class CheckboxGroup<Schema> extends React.Component<
  Props<Schema>,
  State
> {
  state = getInitialState(this.props);
  ...
}

In getInitialState, typescript is complaining that groceries is not an array. So in an attempt to force override the type, I tried this:

  // Type 'Schema[keyof Schema & string]' is not assignable to type 'Groceries'.
  //     Type 'Schema[string]' is not assignable to type '{ id: string; value: string; }[]'.ts(2322)
  const groceries: Groceries = props.values["groceries"];
  const values = groceries.map(({ value }) => value);

I can typecast it this way, then the error goes away - but this is obviously not ideal:

  const groceries: Groceries = (props.values["groceries"] as unknown) as Groceries;
  const values = groceries.map(({ value }) => value);
  1. What's going on here? Why isn't Typescript inferring that values["groceries"] is an Array?

  2. What would be the ideal way to make Typescript happy here?

1 Answers

You had a few errors in your typescript declarations. After fixing them everything works as expected: Playground Link

The main issue was const getInitialState = (props: Props) because Props was a generic type (but it didn't need to be, at least in the current implementation).

Generally you should only use generics if you need to change them dynamically (use T for example, not an exact type because that might create confusion).

type FormikProps = {
  values: Schema
}

type Groceries = Array<{
  id: string;
  value: string;
}>

type Schema = {
  firstName: string;
  lastName: string;
  groceries: Groceries
}

type Props = {
  name: keyof Schema & string;
} & FormikProps

type State = {
  isBananaSelectedInGroceries: boolean;
}

const getInitialState = (props: Props) => {
  const groceries = props.values["groceries"];
  const values = groceries.map(({ value }) => value);
}
Related