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'
Solution: give explicit typings to your variable initSubmissionState
:
const initSubmissionState: Partial<Record<'a' | 'b' | 'c' | 'd', string>> = { d:'d' }
initSubmissionState.a = 'a'
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] = ''
})
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.