Home > Software engineering >  TypeScript not recognizing values set on object within scope of a forEach loop before its passed as
TypeScript not recognizing values set on object within scope of a forEach loop before its passed as

Time:02-16

Im trying to pass an object prop into a functional component that has a prop type definition. I first set the value for each of the required prop type attributes within the scope of a forEach loop, which TypeScript cant seem to recognize:

const preparePayload = (initPayload) => {
  const initSubmissionState = {'e':'e'}
  const attributes = ['a', 'b', 'c','d']

  attributes.forEach (a => {
    if (initPayload.suggested_attributes && initPayload.suggested_attributes[a]) {
      initSubmissionState[a] = initPayload.suggested_attributes[a]
    } else if (initPayload[a]){
      initSubmissionState[a] = initPayload[a]
    } else{
      initSubmissionState[a] = ''
    }
  })
  return initSubmissionState
}

interface Component2Props {
  a: string,
  b: string,
  c: string,
  d: string
}

const Component2 = (props:Component2Props) => {
  return (
    <>
      <div>{props.a}</div>
      <div>{props.b}</div>
      <div>{props.c}</div>
      <div>{props.d}</div>
    </>
  )
}

const initPayload = {
  a: 'a',
  c: 'c',
  suggested_attributes: {
    a:'A'
  }
}

const Component = (props) => {
  const payload = preparePayload(props)

  return (
    <Component2 {...payload} />
  )
}


I am getting a type error passing the payload into Component2: Type '{ e: string; }' is missing the following properties from type 'Component2Props': a, b, c, d

Do I have to explicitly set each of the attributes in the helper function for TypeScript to allow it as props?

CodePudding user response:

There are two components to your issue. First you try to assign unknown keys (a, b and c) to your object initSubmissionState, which only knows the property d. Short example:

const initSubmissionState = { d:'d' }
initSubmissionState.a = 'a'

TypeScript Sandbox

Solution: give explicit typings to your variable initSubmissionState:

const initSubmissionState: Partial<Record<'a' | 'b' | 'c' | 'd', string>> = { d:'d' }
initSubmissionState.a = 'a'

TypeScript Sandbox

The second component is that typescript can't properly interfere the type of your attributes array. It by default selects the smallest common supertype for the array, in this example string, which means the following won't work:

const initSubmissionState: Partial<Record<'a' | 'b' | 'c' | 'd', string>> = { d:'d' }
const attributes = ['a', 'b', 'c', 'd']
attributes.forEach(attr => {
  // TypeScript interferes attr to be of type string, not 'a' | 'b' | 'c' | 'd'
  initSubmissionState[attr] = '' // error because initSubmissionState only has keys 'a', 'b', 'c' and 'd' but TypeScript thinks attr is any string
})

The last step is to now let TypeScript properly interfere the type of your attributes array:

const asStringLiteralArray = <T extends string>(...values: T[]) => values

const initSubmissionState: Partial<Record<'a' | 'b' | 'c' | 'd', string>> = { d:'d' }
const attributes = asStringLiteralArray('a', 'b', 'c', 'd')
attributes.forEach(attr => {
  initSubmissionState[attr] = ''
})

TypeScript Sandbox

Let's now put it all together:

const preparePayload = (initPayload: Record<string, string>) => {
  const initSubmissionState: Partial<Component2Props> = {'d':'d'}
  attributes.forEach(a => {
    if (a in initPayload) {
      initSubmissionState[a] = initPayload[a]
    } else {
      initSubmissionState[a] = ''
    }
  })

  return initSubmissionState as Component2Props
}

const asStringLiteralArray = <T extends string>(...values: T[]) => values
const attributes = asStringLiteralArray('a', 'b', 'c','d')

type Component2Props = Record<typeof attributes[0], string>;

const Component2 = (props:Component2Props) => {
  return (
    <>
      <div>{props.a}</div>
      <div>{props.b}</div>
      <div>{props.c}</div>
    </>
  )
}

const initPayload = {
  a: 'a',
  c: 'c'
}

const Component = (props) => {
  const payload = preparePayload(props)

  return (
    <Component2 {...payload} />
  )
}

Edit: Please try to build a minimal executable example next time, this would enable others to try their fixes without much additional work.

  • Related