I saw a question on Reactiflux today...
What's the best way to handle default props on a component? Should it ever be typed as optional since React will inject your defaults anyway? I'm running into compiler errors saying the optional values are possibly undefined.
The error being referred to is
error TS2532: Object is possibly 'undefined'.
I'll set up the scenario using a component that displays a fractional number. Suppose you have Props that look like this:
export type Props = { numerator: number; denominator?: number; // This is optional};
And you want to be able to specify fractions like this:
<Fraction numerator={ 1 } denominator={ 2 }/> // valid<Fraction numerator={ 3 }/> // valid<Fraction denominator={ 5 }/> // ERROR
Stateless Functional Component
First of all, my go-to answer is that you should be using stateless functional components whenever possible. And, in that case, you can trivially assign defaults when you destructure your props.
Here is an example of destructuring inline in the constructor with a default value:
export const Fraction = ({ numerator, denominator = 1 }: Props) => <span title={ `${numerator / denominator}` }> { numerator } / { denominator } </span>;
Or, if you prefer to be more verbose:
export const Fraction = (props: Props) => { const { numerator, denominator = 1 } = props; const value = numerator / denominator; // no error return ( <span title={ `${value}` }> { numerator } / { denominator } </span> );};
No error.
It Just Works™ as a stateless functional component.
Class-Based Component
Unfortunately, sometimes you might be stuck with a class-based component.
In the simple case where you're just using the property in one place, you can destructure with default value just like we did in the stateless functional component example.
export class Fraction extends React.Component<Props> { render() { const { numerator, denominator = 1 } = this.props; const value = numerator / denominator; // no error return ( <span title={ `${value}` }> { numerator } / { denominator } </span> ); }}
React defaultProps
But what if you need to use the property in a few places?
As a good developer, you know it's bad to repeat yourself (DRY principle on Wikipedia).
We definitely don't want to have duplicate default values scattered throughout our component -- this is just asking for bugs. Namely, if/when the default value changes later on and you miss changing one of the places it was hardcoded.
To make our lives easier, React has a built-in mechanism for supplying default properties. It's called defaultProps
.
However, this mechanism is what causes the error because TypeScript doesn't know that React is going to inject defaults.
The following code shows how to generate the error with default properties configured.
export class Fraction extends React.Component<Props> { static defaultProps = { denominator: 1 } as Props; render() { const { numerator, denominator } = this.props; const value = numerator / denominator; // error TS2532: Object is possibly 'undefined'. return ( <span title={ `${value}` }> { numerator } / { denominator } </span> ); }}
Solution
In TypeScript you can use the exclamation mark (!
) as a hint to the type checker that you know the value is going to be defined.
export class Fraction extends React.Component<Props> { static defaultProps = { denominator: 1 } as Props; render() { const { numerator, denominator } = this.props; const value = numerator / denominator!; // Ooh, exclamation mark! return ( <span title={ `${value}` }> { numerator } / { denominator } </span> ); }}
Now the class compiles without errors.
Further Reading
- Non-null assertion operator
- 5 Strategies to Prevent Bugs in a Large Redux Codebase