J. Hesters
J. Hesters

Reputation: 14748

TypeScript Rest Props - allow any props or tell the component which props they're going to be

I'm building a React app with TypeScript. I have a RequiresPermission component, that based on a predicate, should render one or the other component and forward all props.

type Props = {
  NotPermittedComponent: React.ComponentType;
  PermittedComponent: React.ComponentType;
  isPermitted: boolean;
};

const RequiresPermisson = ({
  NotPermittedComponent,
  PermittedComponent,
  isPermitted,
  ...rest
}: Props) =>
  isPermitted ? (
    <PermittedComponent {...rest} />
  ) : (
    <NotPermittedComponent {...rest} />
  );

export default RequiresPermisson;

When I render the component, TypeScript yells about RequiresPermission in:

const PERMITTED_TEXT = 'permitted';
const NOT_PERMITTED_TEXT = 'not-permitted';

type TestPropsProps = {
  text: string;
};

const NotPermittedTestComponent: React.FunctionComponent<TestPropsProps> = ({
  text,
}) => (
  <div>
    <span>{NOT_PERMITTED_TEXT}</span>
    {text}
  </div>
);

const PermittedTestComponent: React.FunctionComponent<TestPropsProps> = ({
  text,
}) => (
  <div>
    <span>{PERMITTED_TEXT}</span>
    {text}
  </div>
);

const createProps = ({
  NotPermittedComponent = NotPermittedTestComponent,
  PermittedComponent = PermittedTestComponent,
  isPermitted = false,
  text = 'foo',
} = {}) => ({
  NotPermittedComponent,
  PermittedComponent,
  isPermitted,
  text,
});

const props = createProps();
render(<RequiresPermission {...props} />);

saying:

Type '{ NotPermittedComponent: FunctionComponent<TestPropsProps>; PermittedComponent: FunctionComponent<TestPropsProps>; isPermitted: boolean; text: string; }' is not assignable to type '{ NotPermittedComponent: ComponentType<{}>; PermittedComponent: ComponentType<{}>; isPermitted: boolean; }'.
  Types of property 'NotPermittedComponent' are incompatible.
    Type 'FunctionComponent<TestPropsProps>' is not assignable to type 'ComponentType<{}>'.
      Type 'FunctionComponent<TestPropsProps>' is not assignable to type 'FunctionComponent<{}>'.
        Types of parameters 'props' and 'props' are incompatible.
          Type '{ children?: ReactNode; }' is not assignable to type 'PropsWithChildren<TestPropsProps>'.
            Property 'text' is missing in type '{ children?: ReactNode; }' but required in type 'TestPropsProps'.

I also tried Record<string, unknown> as props but that doesn't work either.

How can you fix this to either pass the props or allow any (not the type any) props so the ...rest parameter works?

Upvotes: 4

Views: 4097

Answers (2)

ian
ian

Reputation: 1171

Not sure if this is the most elegant solution, but I have just combined all the prop types. This gives you type checking for all the props. I have to forward all the props because I can not make sure something like isPermitted is not required on a sub component.

interface Props <A, B>{
  NotPermittedComponent: React.ComponentType<A>;
  PermittedComponent: React.ComponentType<B>;
  isPermitted: boolean;
}

const RequiresPermisson = <A, B>(props: Props<A, B> & A & B): JSX.Element => {
  const {
    NotPermittedComponent,
    PermittedComponent,
    isPermitted,
  } = props
  return isPermitted ? (
    <PermittedComponent {...props} />
  ) : (
    <NotPermittedComponent {...props} />
  )
}

Upvotes: 2

Gabriel Petersson
Gabriel Petersson

Reputation: 10412

You need generic types from typescript: https://www.typescriptlang.org/docs/handbook/generics.html

In short, these are dynamic types that depends on what is inputted.

In the code below, we assign all props that are passed to the type T, and tell typescript that the props are: required props NotPermittedComponent PermittedComponent isPermitted along with all of the rest, which in this case becomes the type T.

type Props = {
  NotPermittedComponent: React.ComponentType;
  PermittedComponent: React.ComponentType;
  isPermitted: boolean;
};

const RequiresPermisson = <T extends Record<string, unknown>>({
  NotPermittedComponent,
  PermittedComponent,
  isPermitted,
  ...rest
}: Props & T) =>
  isPermitted ? (
    <PermittedComponent {...rest} />
  ) : (
    <NotPermittedComponent {...rest} />
  );

export default RequiresPermisson;

Upvotes: 0

Related Questions