cyberwombat
cyberwombat

Reputation: 40064

Typescript declaration for conditional react props

I have a React component whose type is based on a runtime variable (isMock) and having difficulties with getting the TS declarations to work:

The component is either MockedProvider or ApolloProvider from @apollo/client which have two different props declaration.

  const isMock: boolean = true // hardcoded for now...

  const GraphQLProvider = isMock ? MockedProvider : ApolloProvider
  const mockProps = { ... }
  const realProps = { ... }

  type ProviderType = typeof MockedProvider | typeof ApolloProvider
  
  const GraphQLProvider: ProviderType = isMock ? MockedProvider : ApolloProvider

  type ProviderProps = React.ComponentProps<typeof GraphQLProvider>

  const graphQLParams: ProviderProps = isMock ? mockProps : realProps

  return (
    <GraphQLProvider {...graphQLParams}> // Error here...
      ...
    </GraphQLProvider>
  )

The above does not work. How would I correctly write declarations to allow for the condition isMock?

I didn't post the error since its quite verbose but basically it tells me that one or more props is missing even though it's not for the determined component.

Upvotes: 0

Views: 86

Answers (1)

branoholy
branoholy

Reputation: 963

The main problem is that you have two union types (ProviderType and ProviderProps) that don't have any relation to each other. At some point, you have to narrow down the types to use the right props for the corresponding components.

See the following example. There are two components Foo and Bar that have completely different props. The Baz component is used to render one of them based on the specified props. Here's the important thing. The BazProps type is a union type that is narrowed down by the if statement via Type Guards. I used the in operator in Baz and a custom user-defined type guard in AlternativeBaz.

import React from 'react';

// Foo
interface FooProps {
  foo: string;
}

const Foo = ({ foo }: FooProps) => <>Foo {foo}</>;

// Bar
interface BarProps {
  bar: number;
}

const Bar = ({ bar }: BarProps) => <>Bar {bar}</>;

// Baz
type BazProps = FooProps | BarProps;

const Baz = (props: BazProps) => {
  if ('foo' in props) {
    return <Foo {...props} />; // props is FooProps
  } else {
    return <Bar {...props} />; // props is BarProps
  }
};

// Alternative Baz
const isFooProps = (props: BazProps): props is FooProps => 'foo' in props;

const AlternativeBaz = (props: BazProps) => {
  if (isFooProps(props)) {
    return <Foo {...props} />; // props is FooProps
  } else {
    return <Bar {...props} />; // props is BarProps
  }
};

// App
const fooProps: FooProps = { foo: 'Foo' };
const barProps: BarProps = { bar: 42 };

interface AppProps {
  isFoo: boolean;
}

export const App = ({ isFoo }: AppProps) => {
  const bazProps = isFoo ? fooProps : barProps;

  return (
    <>
      <Baz {...bazProps} />
      <AlternativeBaz {...bazProps} />
    </>
  );
};

Upvotes: 1

Related Questions