0

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:

enter image description here

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...

3
  • Try to put it in an if block instead, the compiler may interrupt it then. In any case you can make reproducible example in TS playground and show us the error. Commented May 20, 2021 at 19:43
  • Simple solution interface HelpMessage { headerText?: string; footerText?: string; message?: string; helpText?: string; } interface FieldProps { name: string; helpMessage?: HelpMessage; } if( helpText ){ }else{ } Basicaally typescript doen't allow to change the types. Second solution to use the unknown and then use the typecasting with the models Commented May 20, 2021 at 20:03
  • Thank you but is there any way to solve this issue using some kind of typescript conditional? Commented May 20, 2021 at 21:06

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.