Reputation: 1824
I am passing a prop which can be string or an object to a react functional component, an example below. It works without typescript. But, when I add typescript, it does not seem to understand that the prop (e.g. helpMessage below) can be either string or an object.
I get this error
TS2339: Property 'message' does not exist on type 'string | HelpMessage'. Property 'message' does not exist on type 'string'.
import React, { FC } from 'react';
interface HelpMessage {
headerText?: string;
footerText?: string;
message?: string;
}
interface FieldProps {
name: string;
helpMessage?: string | HelpMessage;
}
const ConditionalPropsComponent: FC<FieldProps> = ({ name, helpMessage }) => {
const helpMessageIsString = typeof helpMessage === 'string';
const helpText =
helpMessage && helpMessageIsString ? helpMessage : helpMessage?.message;
return (
<div>
{helpText && !helpMessageIsString && (
<div>
<p>{helpMessage?.headerText}</p>
<p>{helpMessage?.message}</p>
<p>{helpMessage?.footerText}</p>
</div>
)}
{helpText && helpMessageIsString && <p>{helpText}</p>}
</div>
);
};
export default ConditionalPropsComponent;
The error appears where the red wiggly lines are in the screenshot below:
I created a code sandbox, so that you can see the issue. https://codesandbox.io/s/github/nfabacus/React-typescript-dynamic-type-issue/tree/main/?file=/src/index.tsx:217-226
Basically, extracting the condition to const helpMessageIsString = typeof helpMessage === 'string';
throws the type error, but if I replace every one of them with typeof helpMessage === 'string'
, error disappears, but this is repetitive..
also, below works but feels hacky - helpMessage is either HelpMessage or undefined. When I pass string, it is neither the HelpMessage object or undefined, but typescript does not complain..which is strange.
interface HelpMessage {
headerText?: string;
footerText?: string;
message?: string;
}
interface FieldProps {
name: string;
helpMessage?: HelpMessage;
}
const ConditionalPropsComponent: React.FC<FieldProps> = ({
name,
helpMessage
}) => {
const helpMessageIsString = typeof helpMessage === 'string';
const helpText = helpMessageIsString ? helpMessage : helpMessage?.message;
....
Typescript is great, but in this particular case, I feel a bit disappointed with Typescript, if there is not a good solution for this...
Upvotes: 0
Views: 116
Reputation: 53874
What you lack in this example is Type Assertion and Type Inference.
Typescript is a compiler, it can't interrupt types in run time.
When you writing such code, Typescript deduce helpMessageIsString
type as boolean
and not as "if its a truthy boolean so helpMessage
is string".
// helpMessageIsString is boolean
const helpMessageIsString = typeof helpMessage === "string";
// Typescript can't interrupt if the boolean has a meaning for true
// v string | HelpMessage
const helpText = helpMessageIsString ? helpMessage : helpMessage?.message;
The most simple way to make it work, is using Type Inference by condition blocks:
if (typeof helpMessage === "string") {
// In this scope, helpMessage is a STRING
} else {
// In this scope, helpMessage is of type HelpMessage
}
For other unreadable solutions, just pick one of available APIs in type assertion docs.
Upvotes: 1