Per Enström
Per Enström

Reputation: 1086

Passing union typed props to react child component requiring one of the types in the union

In Typescript+React. I've got two components (modules) with specific types specified with an interface. In addition I've got one parent component which takes an array with a union specified as props.

The child components:

Module One

interface IModuleOneProps {
  moduleOneSpecificProp: string;
  module_type: sting;
  commonProp: string;
}

const ModuleOne: React.SFC<IModuleOneProps> = props => {
  return (
    <p>{moduleOneSpecificProp + " " + commonProp}</p>
  );
};

export default ModuleOne;

Module Two

interface IModuleTwoProps {
  moduleTwoSpecificProp: string;
  module_type: sting;
  commonProp: string;
}

const ModuleTwo: React.SFC<IModuleTwoProps> = props => {
  return (
    <p>{moduleTwoSpecificProp + " " + commonProp}</p>
  );
};

export default ModuleTwo;

The container component which contains an array of modules, which can be of both types:

interface IContainerModuleProps {
modules: Array<
    IModuleOneProps | IModuleTwoProps
  >;
}

class ContainerModule extends React.Component<IContainerModuleProps> {

  public render() {
    const module = this.props.modules[1];
    switch (module.module_type) {
      case 'module_one':
        return <ModuleOne {...module} />;
      case 'module_two':
        return <ModuelTwo {...module} />;
    }
  }
}

But TypeScript won't let me spread module when rendering the components. It complains about the types.

Type '{ commonProp: string; moduleOneSpecificProp: string; }' is not assignable to type 'IntrinsicAttributes & IModuleOneProps & { children?: ReactNode; }'.
  Type '{ commonProp: string; moduleTwoSpecificProp: string; }' is not assignable to type 'IntrinsicAttributes & IModuleOneProps & { children?: ReactNode; }'.
    Property 'moduleOneSpecificProp' is missing in type '{ commonProp: string; moduleTwoSpecificProp: string; }'.

Is there a quick fix I'm not seeing here? Or do I have to rebuild the prop object (module) before passing it down to the child component?

Upvotes: 1

Views: 1396

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

You need to define the module_type property as a string literal type (a string type that an have only one value) if you do so, then the switch will type guard correctly:

interface IModuleOneProps {
    moduleOneSpecificProp: string;
    commonProp: string;
    module_type: "module_one"
}
interface IModuleTwoProps {
    moduleTwoSpecificProp: string;
    commonProp: string;
    module_type: "module_two"
}
interface IContainerModuleProps {
    modules: Array<
    IModuleOneProps | IModuleTwoProps
    >;
}

public render() {
    const module = this.props.modules[1];
    switch (module.module_type) {
        case 'module_one':
            return <ModuleOne {...module} />;
        case 'module_two':
            return <ModuleTwo {...module} />;
    }
}

It is also perfectly possible to use an enum for the string literals to avoid being dependent on magic strings:

enum MODULE_NAME {
  MODULE_ONE,
  MODULE_TWO
}

And then use MODULE_NAME.MODULE_ONE on both the interface and in the render method.

Upvotes: 1

Related Questions