0

Here is a reducer used in React's useReducer

export const formReducer = (state: FormState, action: Action) => {
  const { data, type } = action

  switch (type) {
    case 'updated_input': {
      const { value, hasError, error, name, isFormValid } = data
      return {
        ...state,
        [name]: {
          ...(state[name as keyof FormState] as {}),
          value,
          hasError,
          error,
        },
        isFormValid,
      }
    }
    case 'updated_nested_input': {
      const { value, hasError, error, isFormValid, nestedKey, name } = data
      return {
        ...state,
        [nestedKey]: {
          ...state[nestedKey],
          [name]: {
            ...(state[nestedKey][name as keyof (Address | AdminUser)] as {}),
            value,
            hasError,
            error,
            nestedKey,
          },
        },
        isFormValid,
      }
    }
    case 'focus_out_input': {
      const { name, isFormValid, touched, hasError, error } = data
      return {
        ...state,
        [name]: {
          ...(state[name as keyof FormState] as {}),
          touched,
          hasError,
          error,
        },
        isFormValid,
      }
    }
    case 'focus_out_nested_input': {
      const { touched, isFormValid, nestedKey, name, hasError, error } = data
      return {
        ...state,
        [nestedKey]: {
          ...state[nestedKey],
          [name]: {
            ...(state[nestedKey][name as keyof (Address | AdminUser)] as {}),
            touched,
            nestedKey,
            hasError,
            error,
          },
        },
        isFormValid,
      }
    }
    case 'submit_error': {
      const { submitErrors } = data
      return {
        ...state,
        submitErrors,
      }
    }

    default:
      throw new Error(`Unknown action type: ${type}`)
  }
}

Here is my Action Type. I am using unions since there can be multiple shapes for my Action Type.

import { AxiosError } from 'axios'

export type NestedKey = 'address' | 'adminUser'
export interface StatePropObj {
  value: string
  error: string
  nestedKey: NestedKey | boolean
  touched: boolean
  hasError: boolean
}

export type UpdatedInputActionData = StatePropObj & {
  submitErrors?: AxiosError | boolean
  isFormValid: boolean
  name: string
}
export interface UpdatedNestedInputActionData extends UpdatedInputActionData {
  nestedKey: NestedKey
}


export type Action =
  | { type: 'updated_nested_input'; data: UpdatedInputActionData }
  | { type: 'submit_error'; data: { submitErrors: AxiosError | boolean } }
  | { type: 'updated_input'; data: UpdatedInputActionData }
  | {
      type: 'focus_out_input'
      data: {
        touched: boolean
        name: string
        isFormValid: boolean
        error: string
        hasError: boolean
      }
    }
  | {
      type: 'focus_out_nested_input'
      data: {
        touched: boolean
        name: string
        isFormValid: boolean
        nestedKey: NestedKey
        error: string
        hasError: boolean
      }
    }

I am receiving a lot of tsc errors, specifically TS2339 errors:

Property 'value' does not exist on type 'UpdatedInputActionData | { submitErrors: boolean | AxiosError; } | { touched: boolean; name: string; isFormValid: boolean; error: string; hasError: boolean; } | { ...; }'.

49 const { value, hasError, error, name, isFormValid } = data

The error doesn't find the values destructured in the updated_input and updated_nested_input case.

I thought that UpdatedInputActionData would have all props from StatePropObj and then the additional ones I defined. I am essentially "extending" the type here.

I tried this with an interface and the extension keyword and received the same results. Why am I receiving this typescript error when the value is defined on the type?

Note: I am using TS version: "typescript": "^3.8.3"

HelloWorld
  • 10,529
  • 10
  • 31
  • 50

1 Answers1

0

This feature is only available after typescript v4.6, so first solution is upgrade typescript.

If it is not possible (in the short time), you need to write the reducer it like the following to let type narrowing works:

export const formReducer = (state: FormState, action: Action) => {
  switch (action.type) {
    // ...
    case 'focus_out_input': {
      const { name, isFormValid, touched, hasError, error } = action.data
      return {
        ...state,
        [name]: {
          ...(state[name as keyof FormState] as {}),
          touched,
          hasError,
          error,
        },
        isFormValid,
      }
    }
    // ...
  }
}
Jerryh001
  • 766
  • 2
  • 11