Skip to content

Props requiring casting on complex type (and defaultProps not settable) #28614

Open
@dfee

Description

@dfee

Search Terms:
defaultProps React.RefForwardingComponent ExoticComponent

Code

// import { cx } from "emotion";
import * as React from "react";

// import modifiers, { ModifierProps } from "../../modifiers";

interface RenderAsExoticComponent<
  TOwnProps,
  TDefaultComponent extends
    | keyof JSX.IntrinsicElements
    | React.ComponentType<any>
>
  extends Pick<
    React.ForwardRefExoticComponent<any>,
    keyof React.ForwardRefExoticComponent<any>
  > {
  (
    props: React.ComponentPropsWithRef<TDefaultComponent> &
      TOwnProps & { renderAs?: never },
  ): JSX.Element | null;
  <TAsComponent extends keyof JSX.IntrinsicElements | React.ComponentType<any>>(
    props: React.ComponentPropsWithRef<TAsComponent> &
      TOwnProps & { renderAs: TAsComponent },
  ): JSX.Element | null;
}

function renderAsComponent<
  TOwnProps,
  TDefaultElement extends React.ComponentType<any> | keyof JSX.IntrinsicElements
>(
  factory: React.RefForwardingComponent<
    any,
    TOwnProps & {
      renderAs?: React.ComponentType<any> | keyof JSX.IntrinsicElements;
      className?: string;
    }
  >,
  defaultElement: TDefaultElement,
) {
  const forward = React.forwardRef(factory);
  forward.defaultProps = { renderAs: defaultElement };
  // todo: apparently a bug, use workaround
  // forward.defaultProps = {};
  // forward.defaultProps.renderAs = defaultElement;
  return forward as RenderAsExoticComponent<TOwnProps, TDefaultElement>;
}

interface ModifierProps {
  textColor?: "white" | "black";
  pull?: "left" | "right";
}

const Element = renderAsComponent<ModifierProps, "div">(
  ({ className, renderAs, ...allProps }, ref) => {
    const props = {
      // className: cx(className, modifiers.classNames(allProps)) || undefined,
      ref,
      // ...modifiers.clean(allProps),
    };
    return React.createElement(renderAs!, props);
  },
  "div",
);

export default Element;
export const Example: React.SFC<{}> = () => (
  <Element textColor="white" pull={"left" as "left"}>
    Child
  </Element>
);

Expected behavior:

  1. defaultProps can be set directly
  2. props work without casting

Actual behavior:

  1. defaultProps cannot be set directly (see workaround in code)
  2. props do not work without being cast.

Playground Link:

Related Issues:
No.

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions