Wrapping/Mirroring

Wrapping/Mirroring a HTML Element

Usecase: you want to make a <Button> that takes all the normal props of <button> and does extra stuff.

Strategy: extend React.ComponentPropsWithoutRef<'button'>

// usage
function App() {
    // Type '"foo"' is not assignable to type '"button" | "submit" | "reset" | undefined'.(2322)
    // return <Button type="foo"> sldkj </Button>

    // no error
    return <Button type="button"> text </Button>;
}

// implementation
export interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
    specialProp?: string;
}
export function Button(props: ButtonProps) {
    const { specialProp, ...rest } = props;
    // do something with specialProp
    return <button {...rest} />;
}

See this in the TS Playground

Forwarding Refs: As the React docs themselves note, most usecases will not need to obtain a ref to the inner element. But for people making reusable component libraries, you will need to forwardRef the underlying element, and then you can use ComponentPropsWithRef to grab props for your wrapper component. Check our docs on forwarding Refs for more.

In future, the need to forwardRef may go away in React 17+, but for now we still have to deal with this. ๐Ÿ™ƒ

Why not ComponentProps or IntrinsicElements or [Element]HTMLAttributes or HTMLProps or HTMLAttributes?

ComponentProps

You CAN use ComponentProps in place of ComponentPropsWithRef, but you may prefer to be explicit about whether or not the component's refs are forwarded, which is what we have chosen to demonstrate. The tradeoff is slightly more intimidating terminology.

More info: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forward_and_create_ref/

Maybe JSX.IntrinsicElements or React.[Element]HTMLAttributes

There are at least 2 other equivalent ways to do this, but they are more verbose:

// Method 1: JSX.IntrinsicElements
type btnType = JSX.IntrinsicElements["button"]; // cannot inline or will error
export interface ButtonProps extends btnType {} // etc

// Method 2: React.[Element]HTMLAttributes
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>

Looking at the source for ComponentProps shows that this is a clever wrapper for JSX.IntrinsicElements, whereas the second method relies on specialized interfaces with unfamiliar naming/capitalization.

Note: There are over 50 of these specialized interfaces available - look for HTMLAttributes in our @types/react commentary.

Ultimately, we picked the ComponentProps method as it involves the least TS specific jargon and has the most ease of use. But you'll be fine with either of these methods if you prefer.

Definitely not React.HTMLProps or React.HTMLAttributes

This is what happens when you use React.HTMLProps:

export interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
    specialProp: string;
}
export function Button(props: ButtonProps) {
    const { specialProp, ...rest } = props;
    // ERROR: Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.
    return <button {...rest} />;
}

It infers a too-wide type of string for type, because it uses AllHTMLAttributes under the hood.

This is what happens when you use React.HTMLAttributes:

export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
    /* etc */
}
// usage
function App() {
    // Property 'type' does not exist on type 'IntrinsicAttributes & ButtonProps'
    return <Button type="submit"> text </Button>;
}

Wrapping/Mirroring a Component

TODO: this section needs work to make it simplified.

Usecase: same as above, but for a React Component you don't have access to the underlying props

const Box = (props: React.CSSProperties) => <div style={props} />;

const Card = (
    { title, children, ...props }: { title: string } & $ElementProps<typeof Box> // new utility, see below
) => (
    <Box {...props}>
        {title}: {children}
    </Box>
);

Strategy: extract a component's props by inferring them

Example:

// ReactUtilityTypes.d.ts
declare type $ElementProps<T> = T extends React.ComponentType<infer Props> ? (Props extends object ? Props : never) : never;

Usage:

import * as Recompose from 'recompose';
export const defaultProps = <C extends React.ComponentType, D extends Partial<$ElementProps<C>>>(
    defaults: D,
    Component: C
): React.ComponentType<$ElementProps<C> & Partial<D>> => Recompose.defaultProps(defaults)(Component);

thanks dmisdm

:new: You should also consider whether to explicitly forward refs:

// base button, with ref forwarding
type Props = { children: React.ReactNode; type: 'submit' | 'button' };
export type Ref = HTMLButtonElement;
export const FancyButton = React.forwardRef<Ref, Props>((props, ref) => (
    <button ref={ref} className="MyCustomButtonClass" type={props.type}>
        {props.children}
    </button>
));

// second layer button, no need for forwardRef (TODO: doublecheck this)
export interface DoubleWrappedProps extends React.ComponentPropsWithRef<typeof FancyButton> {
    specialProp?: string;
}
export function DoubleWrappedButton(props: DoubleWrappedProps) {
    const { specialProp, ref, ...rest } = props;
    return <button ref={ref} {...rest} />;
}

// usage
function App() {
    const btnRef = React.useRef<HTMLButtonElement>(null!);
    return (
        <DoubleWrappedButton type="button" ref={btnRef}>
            {' '}
            text{' '}
        </DoubleWrappedButton>
    );
}

TS Playground link

Polymorphic Components (e.g. with as props)

"Polymorphic Components" = passing a component to be rendered, e.g. with as props

ElementType is pretty useful to cover most types that can be passed to createElement e.g.

function PassThrough(props: { as: React.ElementType<any> }) {
    const { as: Component } = props;

    return <Component />;
}

You might also see this with React Router:

const PrivateRoute = ({ component: Component, ...rest }: PrivateRouteProps) => {
    const { isLoggedIn } = useAuth();

    return isLoggedIn ? <Component {...rest} /> : <Redirect to="/" />;
};

For more info you can refer to these resources:

Thanks @eps1lon and @karol-majewski for thoughts!

Generic Components

Just as you can make generic functions and classes in TypeScript, you can also make generic components to take advantage of the type system for reusable type safety. Both Props and State can take advantage of the same generic types, although it probably makes more sense for Props than for State. You can then use the generic type to annotate types of any variables defined inside your function / class scope.

interface Props<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}
function List<T>(props: Props<T>) {
    const { items, renderItem } = props;
    const [state, setState] = React.useState<T[]>([]); // You can use type T in List function scope.
    return (
        <div>
            {items.map(renderItem)}
            <button onClick={() => setState(items)}>Clone</button>
            {JSON.stringify(state, null, 2)}
        </div>
    );
}

You can then use the generic components and get nice type safety through type inference:

ReactDOM.render(
    <List
        items={['a', 'b']} // type of 'string' inferred
        renderItem={(item) => (
            <li key={item}>
                {/* Error: Property 'toPrecision' does not exist on type 'string'. */}
                {item.toPrecision(3)}
            </li>
        )}
    />,
    document.body
);

As of TS 2.9, you can also supply the type parameter in your JSX to opt out of type inference:

ReactDOM.render(
    <List<number>
        items={['a', 'b']} // Error: Type 'string' is not assignable to type 'number'.
        renderItem={(item) => <li key={item}>{item.toPrecision(3)}</li>}
    />,
    document.body
);

You can also use Generics using fat arrow function style:

interface Props<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}

// Note the <T extends unknown> before the function definition.
// You can't use just `<T>` as it will confuse the TSX parser whether it's a JSX tag or a Generic Declaration.
// You can also use <T,> https://github.com/microsoft/TypeScript/issues/15713#issuecomment-499474386
const List = <T extends unknown>(props: Props<T>) => {
    const { items, renderItem } = props;
    const [state, setState] = React.useState<T[]>([]); // You can use type T in List function scope.
    return (
        <div>
            {items.map(renderItem)}
            <button onClick={() => setState(items)}>Clone</button>
            {JSON.stringify(state, null, 2)}
        </div>
    );
};

The same for using classes: (Credit: Karol Majewski's gist)

interface Props<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}

interface State<T> {
    items: T[];
}

class List<T> extends React.PureComponent<Props<T>, State<T>> {
    // You can use type T inside List class.
    state: Readonly<State<T>> = {
        items: []
    };
    render() {
        const { items, renderItem } = this.props;
        // You can use type T inside List class.
        const clone: T[] = items.slice(0);
        return (
            <div>
                {items.map(renderItem)}
                <button onClick={() => this.setState({ items: clone })}>Clone</button>
                {JSON.stringify(this.state, null, 2)}
            </div>
        );
    }
}

Though you can't use Generic Type Parameters for Static Members:

class List<T> extends React.PureComponent<Props<T>, State<T>> {
    // Static members cannot reference class type parameters.ts(2302)
    static getDerivedStateFromProps(props: Props<T>, state: State<T>) {
        return { items: props.items };
    }
}

To fix this you need to convert your static function to a type inferred function:

class List<T> extends React.PureComponent<Props<T>, State<T>> {
    static getDerivedStateFromProps<T>(props: Props<T>, state: State<T>) {
        return { items: props.items };
    }
}

Generic components with children

children is usually not defined as a part of the props type. Unless children are explicitly defined as a part of the props type, an attempt to use props.children in JSX or in the function body will fail:

interface WrapperProps<T> {
    item: T;
    renderItem: (item: T) => React.ReactNode;
}

/* Property 'children' does not exist on type 'WrapperProps<T>'. */
const Wrapper = <T extends {}>(props: WrapperProps<T>) => {
    return (
        <div>
            {props.renderItem(props.item)}
            {props.children}
        </div>
    );
};

/*
Type '{ children: string; item: string; renderItem: (item: string) => string; }' is not assignable to type 'IntrinsicAttributes & WrapperProps<string>'.
  Property 'children' does not exist on type 'IntrinsicAttributes & WrapperProps<string>'.
*/

const wrapper = (
    <Wrapper item="test" renderItem={(item) => item}>
        {test}
    </Wrapper>
);

View in the TypeScript Playground

To work around that, either add children to the WrapperProps definition (possibly narrowing down its type, as needed):

interface WrapperProps<T> {
    item: T;
    renderItem: (item: T) => React.ReactNode;
    children: string; // The component will only accept a single string child
}

const Wrapper = <T extends {}>(props: WrapperProps<T>) => {
    return (
        <div>
            {props.renderItem(props.item)}
            {props.children}
        </div>
    );
};

or wrap the type of the props in React.PropsWithChildren (this is what React.FC<> does):

interface WrapperProps<T> {
    item: T;
    renderItem: (item: T) => React.ReactNode;
}

const Wrapper = <T extends {}>(props: React.PropsWithChildren<WrapperProps<T>>) => {
    return (
        <div>
            {props.renderItem(props.item)}
            {props.children}
        </div>
    );
};

Typing Children

Some API designs require some restriction on children passed to a parent component. It is common to want to enforce these in types, but you should be aware of limitations to this ability.

What You CAN Do

You can type the structure of your children: just one child, or a tuple of children.

The following are valid:

type OneChild = React.ReactElement;
type TwoChildren = [React.ReactElement, React.ReactElement];
type ArrayOfProps = SomeProp[];
type NumbersChildren = number[];
type TwoNumbersChildren = [number, number];
Don't forget that you can also use `prop-types` if TS fails you.
Parent.propTypes = {
    children: PropTypes.shape({
        props: PropTypes.shape({
            // could share `propTypes` to the child
            value: PropTypes.string.isRequired
        })
    }).isRequired
};

What You CANNOT Do

The thing you cannot do is specify which components the children are, e.g. If you want to express the fact that "React Router <Routes> can only have <Route> as children, nothing else is allowed" in TypeScript.

This is because when you write a JSX expression (const foo = <MyComponent foo='foo' />), the resultant type is blackboxed into a generic JSX.Element type. (thanks @ferdaber)

Type Narrowing based on Props

What you want:

// Usage
function App() {
    return (
        <>
            {/* ๐Ÿ˜Ž All good */}
            <Button target="_blank" href="https://www.google.com">
                Test
            </Button>
            {/* ๐Ÿ˜ญ Error, `disabled` doesnt exist on anchor element */}
            <Button disabled href="x">
                Test
            </Button>
        </>
    );
}

How to implement: Use type guards!

// Button props
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
    href?: undefined;
};

// Anchor props
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
    href?: string;
};

// Input/output options
type Overload = {
    (props: ButtonProps): JSX.Element;
    (props: AnchorProps): JSX.Element;
};

// Guard to check if href exists in props
const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps => 'href' in props;

// Component
const Button: Overload = (props: ButtonProps | AnchorProps) => {
    // anchor render
    if (hasHref(props)) return <a {...props} />;
    // button render
    return <button {...props} />;
};

View in the TypeScript Playground

Components, and JSX in general, are analogous to functions. When a component can render differently based on their props, it's similar to how a function can be overloaded to have multiple call signatures. In the same way, you can overload a function component's call signature to list all of its different "versions".

A very common use case for this is to render something as either a button or an anchor, based on if it receives a href attribute.

type ButtonProps = JSX.IntrinsicElements['button'];
type AnchorProps = JSX.IntrinsicElements['a'];

// optionally use a custom type guard
function isPropsForAnchorElement(props: ButtonProps | AnchorProps): props is AnchorProps {
    return 'href' in props;
}

function Clickable(props: ButtonProps | AnchorProps) {
    if (isPropsForAnchorElement(props)) {
        return <a {...props} />;
    } else {
        return <button {...props} />;
    }
}

They don't even need to be completely different props, as long as they have at least one difference in properties:

type LinkProps = Omit<JSX.IntrinsicElements['a'], 'href'> & { to?: string };

function RouterLink(props: LinkProps | AnchorProps) {
    if ('href' in props) {
        return <a {...props} />;
    } else {
        return <Link {...props} />;
    }
}
Approach: Generic Components

Here is an example solution, see the further discussion for other solutions. thanks to @jpavon

interface LinkProps {}
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
type RouterLinkProps = Omit<NavLinkProps, 'href'>;

const Link = <T extends {}>(props: LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps) => {
    if ((props as RouterLinkProps).to) {
        return <NavLink {...(props as RouterLinkProps)} />;
    } else {
        return <a {...(props as AnchorProps)} />;
    }
};

<Link<RouterLinkProps> to="/">My link</Link>; // ok
<Link<AnchorProps> href="/">My link</Link>; // ok
<Link<RouterLinkProps> to="/" href="/">
    My link
</Link>; // error
Approach: Composition

If you want to conditionally render a component, sometimes is better to use React's composition model to have simpler components and better to understand typings:

type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
type RouterLinkProps = Omit<AnchorProps, 'href'>;

interface Button {
    as: React.ComponentClass | 'a';
}

const Button: React.FunctionComponent<Button> = (props) => {
    const { as: Component, children, ...rest } = props;
    return (
        <Component className="button" {...rest}>
            {children}
        </Component>
    );
};

const AnchorButton: React.FunctionComponent<AnchorProps> = (props) => <Button as="a" {...props} />;

const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => <Button as={NavLink} {...props} />;

<LinkButton to="/login">Login</LinkButton>;
<AnchorButton href="/login">Login</AnchorButton>;
<AnchorButton href="/login" to="/test">
    Login
</AnchorButton>; // Error: Property 'to' does not exist on type...

You may also want to use Discriminated Unions, please check out Expressive React Component APIs with Discriminated Unions.

Here is a brief intuition for Discriminated Union Types:

type UserTextEvent = {
    type: 'TextEvent';
    value: string;
    target: HTMLInputElement;
};
type UserMouseEvent = {
    type: 'MouseEvent';
    value: [number, number];
    target: HTMLElement;
};
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
    if (event.type === 'TextEvent') {
        event.value; // string
        event.target; // HTMLInputElement
        return;
    }
    event.value; // [number, number]
    event.target; // HTMLElement
}
Take care: TypeScript does not narrow the type of a Discriminated Union on the basis of typeof checks. The type guard has to be on the value of a key and not it's type.
type UserTextEvent = { value: string; target: HTMLInputElement };
type UserMouseEvent = { value: [number, number]; target: HTMLElement };
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
    if (typeof event.value === 'string') {
        event.value; // string
        event.target; // HTMLInputElement | HTMLElement (!!!!)
        return;
    }
    event.value; // [number, number]
    event.target; // HTMLInputElement | HTMLElement (!!!!)
}

The above example does not work as we are not checking the value of event.value but only it's type. Read more about it microsoft/TypeScript#30506 (comment)

Discriminated Unions in TypeScript can also work with hook dependencies in React. The type matched is automatically updated when the corresponding union member based on which a hook depends, changes. Expand more to see an example usecase.

type SingleElement = {
    isArray: true;
    value: string[];
};
type MultiElement = {
    isArray: false;
    value: string;
};
type Props = SingleElement | MultiElement;
function Sequence(p: Props) {
    return React.useMemo(
        () => (
            <div>
                value(s):
                {p.isArray && p.value.join(',')}
                {!p.isArray && p.value}
            </div>
        ),
        [p.isArray, p.value] // TypeScript automatically matches the corresponding value type based on dependency change
    );
}
function App() {
    return (
        <div>
            <Sequence isArray={false} value={'foo'} />
            <Sequence isArray={true} value={['foo', 'bar', 'baz']} />
        </div>
    );
}

See this in TS Playground

In the above example, based on the isArray union member, the type of the value hook dependency changes.

To streamline this you may also combine this with the concept of User-Defined Type Guards:

function isString(a: unknown): a is string {
    return typeof a === 'string';
}

Read more about User-Defined Type Guards in the Handbook.

Props: One or the Other but not Both

Use the in keyword, function overloading, and union types to make components that take either one or another sets of props, but not both:

type Props1 = { foo: string };
type Props2 = { bar: string };

function MyComponent(props: Props1 | Props2) {
    if ('foo' in props) {
        // props.bar // error
        return <div>{props.foo}</div>;
    } else {
        // props.foo // error
        return <div>{props.bar}</div>;
    }
}
const UsageComponent: React.FC = () => (
    <div>
        <MyComponent foo="foo" />
        <MyComponent bar="bar" />
        {/* <MyComponent foo="foo" bar="bar"/> // invalid */}
    </div>
);

View in the TypeScript Playground

Further reading: how to ban passing {} if you have a NoFields type.

Props: Must Pass Both

type OneOrAnother<T1, T2> = (T1 & { [K in keyof T2]?: undefined }) | (T2 & { [K in keyof T1]?: undefined });

type Props = OneOrAnother<{ a: string; b: string }, {}>;

const a: Props = { a: 'a' }; // error
const b: Props = { b: 'b' }; // error
const ab: Props = { a: 'a', b: 'b' }; // ok

Thanks diegohaz

Props: Pass One ONLY IF the Other Is Passed

Say you want a Text component that gets truncated if truncate prop is passed but expands to show the full text when expanded prop is passed (e.g. when the user clicks the text).

You want to allow expanded to be passed only if truncate is also passed, because there is no use for expanded if the text is not truncated.

Usage example:

const App: React.FC = () => (
    <>
        {/* these all typecheck */}
        <Text>not truncated</Text>
        <Text truncate>truncated</Text>
        <Text truncate expanded>
            truncate-able but expanded
        </Text>
        {/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type '{ truncate: true; expanded?: boolean | undefined; }'. */}
        <Text expanded>truncate-able but expanded</Text>
    </>
);

You can implement this by function overloads:

type CommonProps = {
    children: React.ReactNode;
    miscProps?: any;
};

type NoTruncateProps = CommonProps & { truncate?: false };

type TruncateProps = CommonProps & { truncate: true; expanded?: boolean };

// Function overloads to accept both prop types NoTruncateProps & TruncateProps
function Text(props: NoTruncateProps): JSX.Element;
function Text(props: TruncateProps): JSX.Element;
function Text(props: CommonProps & { truncate?: boolean; expanded?: boolean }) {
    const { children, truncate, expanded, ...otherProps } = props;
    const classNames = truncate ? '.truncate' : '';
    return (
        <div className={classNames} aria-expanded={!!expanded} {...otherProps}>
            {children}
        </div>
    );
}

Props: Omit prop from a type

Note: Omit was added as a first class utility in TS 3.5! ๐ŸŽ‰

Sometimes when intersecting types, we want to define our own version of a prop. For example, I want my component to have a label, but the type I am intersecting with also has a label prop. Here's how to extract that out:

export interface Props {
    label: React.ReactNode; // this will conflict with the InputElement's label
}

// this comes inbuilt with TS 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

// usage
export const Checkbox = (props: Props & Omit<React.HTMLProps<HTMLInputElement>, 'label'>) => {
    const { label } = props;
    return (
        <div className="Checkbox">
            <label className="Checkbox-label">
                <input type="checkbox" {...props} />
            </label>
            <span>{label}</span>
        </div>
    );
};

When your component defines multiple props, chances of those conflicts increase. However you can explicitly state that all your fields should be removed from the underlying component using the keyof operator:

export interface Props {
    label: React.ReactNode; // conflicts with the InputElement's label
    onChange: (text: string) => void; // conflicts with InputElement's onChange
}

export const Textbox = (props: Props & Omit<React.HTMLProps<HTMLInputElement>, keyof Props>) => {
    // implement Textbox component ...
};

As you can see from the Omit example above, you can write significant logic in your types as well. type-zoo is a nice toolkit of operators you may wish to check out (includes Omit), as well as utility-types (especially for those migrating from Flow).

Props: Extracting Prop Types of a Component

Sometimes you want the prop types of a component, but it isn't exported.

A simple solution is to use React.ComponentProps:

// a Modal component defined elsewhere
const defaultProps: React.ComponentProps<typeof Modal> = {
    title: 'Hello World',
    visible: true,
    onClick: jest.fn()
};

There are advanced edge cases if you want to extract the prop types of a component taking into account internal props, propTypes, and defaultProps - check our issue here for helper utilities that resolve these.

Props: Render Props

Advice: Where possible, you should try to use Hooks instead of Render Props. We include this merely for completeness.

Sometimes you will want to write a function that can take a React element or a string or something else as a prop. The best Type to use for such a situation is React.ReactNode which fits anywhere a normal, well, React Node would fit:

export interface Props {
    label?: React.ReactNode;
    children: React.ReactNode;
}
export const Card = (props: Props) => {
    return (
        <div>
            {props.label && <div>{props.label}</div>}
            {props.children}
        </div>
    );
};

If you are using a function-as-a-child render prop:

export interface Props {
    children: (foo: string) => React.ReactNode;
}

Something to add? File an issue.

Handling Exceptions

You can provide good information when bad things happen.

class InvalidDateFormatError extends RangeError {}
class DateIsInFutureError extends RangeError {}

/**
 * // optional docblock
 * @throws {InvalidDateFormatError} The user entered date incorrectly
 * @throws {DateIsInFutureError} The user entered date in future
 *
 */
function parse(date: string) {
    if (!isValid(date)) throw new InvalidDateFormatError('not a valid date format');
    if (isInFuture(date)) throw new DateIsInFutureError('date is in the future');
    // ...
}

try {
    // call parse(date) somewhere
} catch (e) {
    if (e instanceof InvalidDateFormatError) {
        console.error('invalid date format', e);
    } else if (e instanceof DateIsInFutureError) {
        console.warn('date is in future', e);
    } else {
        throw e;
    }
}

View in TypeScript Playground

Simply throwing an exception is fine, however it would be nice to make TypeScript remind the consumer of your code to handle your exception. We can do that just by returning instead of throwing:

function parse(date: string): Date | InvalidDateFormatError | DateIsInFutureError {
    if (!isValid(date)) return new InvalidDateFormatError('not a valid date format');
    if (isInFuture(date)) return new DateIsInFutureError('date is in the future');
    // ...
}

// now consumer *has* to handle the errors
let result = parse('mydate');
if (result instanceof InvalidDateFormatError) {
    console.error('invalid date format', result.message);
} else if (result instanceof DateIsInFutureError) {
    console.warn('date is in future', result.message);
} else {
    /// use result safely
}

// alternately you can just handle all errors
if (result instanceof Error) {
    console.error('error', result);
} else {
    /// use result safely
}

You can also describe exceptions with special-purpose data types (don't say monads...) like the Try, Option (or Maybe), and Either data types:

interface Option<T> {
    flatMap<U>(f: (value: T) => None): None;
    flatMap<U>(f: (value: T) => Option<U>): FormikOption<U>;
    getOrElse(value: T): T;
}
class Some<T> implements Option<T> {
    constructor(private value: T) {}
    flatMap<U>(f: (value: T) => None): None;
    flatMap<U>(f: (value: T) => Some<U>): Some<U>;
    flatMap<U>(f: (value: T) => Option<U>): Option<U> {
        return f(this.value);
    }
    getOrElse(): T {
        return this.value;
    }
}
class None implements Option<never> {
    flatMap<U>(): None {
        return this;
    }
    getOrElse<U>(value: U): U {
        return value;
    }
}

// now you can use it like:
let result = Option(6) // Some<number>
    .flatMap((n) => Option(n * 3)) // Some<number>
    .flatMap((n = new None())) // None
    .getOrElse(7);

// or:
let result = ask() // Option<string>
    .flatMap(parse) // Option<Date>
    .flatMap((d) => new Some(d.toISOString())) // Option<string>
    .getOrElse('error parsing string');