I'm writing a web application in ReactJS + Typescript. I've a functional ponent defined like below.
My problem is the following: in the props, for the property exercise
, the parent ponent is passing an object, either initialized empty or of a certain type that I specify, Exercise
. Then Typescript raises the following errors:
[ts] Property 'description' does not exist on type '{} | Exercise'
[ts] Property 'title' does not exist on type '{} | Exercise'
How could I refactor it so that if the object is indeed empty, it will use the default values, and otherwise, use the values passed?
EDIT: Added the other props that I use
type Exercise = {
description: string
id: string
muscles: string
title: string
}
type Props = {
category: string
children?: never
exercise: {} | Exercise
exercises: Array<[string, Exercise[]]>
onSelect: (id: string) => void
}
const Exercises = ({
exercises,
category,
onSelect,
exercise: {
description = 'Please select an exercise',
title = 'Wele!'
}
}: Props) => (
<Grid container>
<Grid item sm>
{/* Some stuff going on with exercises, category and onSelect */ }
</Grid>
<Grid item sm>
<Paper>
<Typography variant="h4">{title}</Typography>
<Typography variant="subtitle1">{description}</Typography>
</Paper>
</Grid>
</Grid>
)
I'm writing a web application in ReactJS + Typescript. I've a functional ponent defined like below.
My problem is the following: in the props, for the property exercise
, the parent ponent is passing an object, either initialized empty or of a certain type that I specify, Exercise
. Then Typescript raises the following errors:
[ts] Property 'description' does not exist on type '{} | Exercise'
[ts] Property 'title' does not exist on type '{} | Exercise'
How could I refactor it so that if the object is indeed empty, it will use the default values, and otherwise, use the values passed?
EDIT: Added the other props that I use
type Exercise = {
description: string
id: string
muscles: string
title: string
}
type Props = {
category: string
children?: never
exercise: {} | Exercise
exercises: Array<[string, Exercise[]]>
onSelect: (id: string) => void
}
const Exercises = ({
exercises,
category,
onSelect,
exercise: {
description = 'Please select an exercise',
title = 'Wele!'
}
}: Props) => (
<Grid container>
<Grid item sm>
{/* Some stuff going on with exercises, category and onSelect */ }
</Grid>
<Grid item sm>
<Paper>
<Typography variant="h4">{title}</Typography>
<Typography variant="subtitle1">{description}</Typography>
</Paper>
</Grid>
</Grid>
)
Share
Improve this question
edited Feb 6, 2019 at 20:03
Théo Lavaux
asked Feb 6, 2019 at 17:02
Théo LavauxThéo Lavaux
1,4543 gold badges25 silver badges58 bronze badges
1
- 1 Related: github./Microsoft/TypeScript/issues/8032 – adiga Commented Feb 6, 2019 at 17:55
2 Answers
Reset to default 3I think something similar to this should work
type Exercise = {
description: string
id: string
muscles: string
title: string
}
type Props = {
exercise: Partial<Exercise>
}
const Exercises = (props: Props) => {
const exercice = {
description:'Please select an exercise',
title: 'Wele!',
...props.exercise
}
return (
<Grid container>
<Grid item sm>
<Paper>
<Typography variant="h4">{exercice.title}</Typography>
<Typography variant="subtitle1">{exercice.description}</Typography>
</Paper>
</Grid>
</Grid>
)
}
edit: align code
So overall I don't think your API design is correct for this ponent. You're basically misusing exercise entity as some default "Wele message stuff", which is rather miss leading to consumers of this ponent.
What I would do, is to provide these intro defaults when there is no exercise present, but would definitely not use exercise prop to assign those defaults.
Next thing, don't use {}
, that's not empty object (you can define empty object like following https://github./Hotell/rex-tils/blob/master/src/guards/types.ts#L39 ) . It used to be a bottom type prior to TS 3.0 ( now unknown
is bottom type ). What does it mean? {}
can be anything except null/undefined:
// all of this is valid !
let foo: {} = 1231
foo = true
foo = { baz: 'bar' }
foo = [1,2,3]
Also if you really wanna support passing "empty" non primitive data types to ponents, prefer null
:
type Props = {
category: string
children?: never
// Maybe type
exercise: null | Exercise
exercises: [string, Exercise[]][]
onSelect: (id: string) => void
}
Anyways if your really wanna keep your API as is. You have following option:
- Extract defaults to constant which needs to be cast to Exercise
const defaultExercise = {
description: 'Please select an exercise',
title: 'Wele!',
} as Exercise
- you need to type narrow exercise prop outside function default parameter, as that's not possible within function params
const Exercises = ({ exercises, category, onSelect, exercise }: Props) => {
// $ExpectType Exercise
const { title, description } = exercise ? exercise : defaultExercise
return <>your markup</>
}
Now while this works it gives you false assumptions. As your exercise
might be a partial one (if defaults are used), which may lead to runtime errors. You'll need additional type narrowing via guards ( if, ternary ).
You can improve this situation on type level, by some type mappings:
// $ExpectType { description: string, title: string, id?: string, muscles?: string }
const defaultExercise = {
description: 'Please select an exercise',
title: 'Wele!',
} as Partial<Exercise> & Required<Pick<Exercise, 'description' | 'title'>>
With that type if you would use id
or muscles
within your ponent, you'll get proper types as they might be undefined, which mirrors correctly our ternary
const {
title, //$ExpectType string
description, //$ExpectType string
id, //$ExpectType string | undefined
} = exercise ? exercise : defaultExercise