jantimon
jantimon

Reputation: 38140

Typescript and React HOC (High Order Components)

With the withBlueBackground high order component I extend a function with a new shade property. (from this medium post)

This shade property should be either 'light' or 'dark'.

Unfortunately these typings won't help us with refactored or wrong props definition.

I attached an example which won't warn although 'ORANGE'matches neither 'light' nor 'dark'.

import * as React from 'react';

type ColorShade = 'light' | 'dark';

export interface InjectedBlueBackgroundProps {
  backgroundColor: string;
}

interface WithBlueBackgroundProps {
  shade?: ColorShade;
}

const getBlueShade = (shade?: ColorShade) => {
  switch (shade) {
    case 'dark':
      return 'navy';
    case 'light':
      return 'skyblue';
    default:
      return 'blue';
  }
};

const withBlueBackground = <P extends InjectedBlueBackgroundProps>(
  UnwrappedComponent: React.ComponentType<P>
) =>
  class WithBlueBackground extends React.Component<
    P & WithBlueBackgroundProps
  > {
    render() {
      return (
        <UnwrappedComponent
          {...this.props}
          backgroundColor={getBlueShade(this.props.shade)}
        />
      );
    }
  };

export default withBlueBackground;

// The following line should throw an type error but does not:
export const Demo = withBlueBackground((props: {shade?: 'ORANGE'}) => {
  return <div>{props.backgroundColor}</div>
});

Is there any solution?

Upvotes: 1

Views: 2565

Answers (1)

aastronaut
aastronaut

Reputation: 33

Some things that make the HOC-call seem strange to me are:

1) the access of this. inside the arrow function

2) The usage of a HOC as a Lower-order-component... accessing this.props.shade from the generic props of the HOC

Please note that you want to give some own props (top-level) and some injected props (low-level) to the wrapped component with only a single HOC.

// main.ts

import React from 'react';

import {Demo} from './demo';

React.render(
  <Demo
    greeting="hello"
    shape="light"
  />,
  document.getElementById('app'),
);

demo file

// demo.ts

import React from 'react';

import withBlueBackground, {InjectedBlueBackgroundProps, OwnBlueBackgroundProps} from './withBlueBackground';

interface OwnDemoProps extends OwnBlueBackgroundProps {
  greeting: string;
}

type DemoProps = OwnDemoProps & InjectedBlueBackgroundProps;

const Demo = (props: DemoProps) => (
  <div>{props.greeting + ' ' + props.backgroundColor}</div>
);

const DemoBlue = withBlueBackground<OwnDemoProps>(Demo);

export {DemoBlue as Demo};

hoc file

// withBlueBackground.ts

import React from 'react';

export type ColorShade = 'light' | 'dark';

const getBlueShade = (shade?: ColorShade) => {
  switch (shade) {
    case 'dark':
      return 'navy';
    case 'light':
      return 'skyblue';
    default:
      return 'blue';
  }
};

export interface OwnBlueBackgroundProps {
  shade?: ColorShade;
}

export interface InjectedBlueBackgroundProps {
  backgroundColor: string;
}

const withBlueBackground = <P extends OwnBlueBackgroundProps>(
  UnwrappedComponent: React.ComponentType<P & InjectedBlueBackgroundProps>
) => class WithBlueBackground extends React.Component<
  P & InjectedBlueBackgroundProps
> {
  public render() {
    return (
      <UnwrappedComponent
        {...this.props}
        backgroundColor={getBlueShade(this.props.shade)}
      />
    );
  }
};

export default withBlueBackground;

Upvotes: 2

Related Questions