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"
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: