2

Apologies in advance for the vague title it's quite hard to tl;dr my issue.

I'm trying to use the React, Recompose and Typescript together but when trying to pass props into my component I get the following error:

[ts] Property 'bar' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<{}, any, any>> & Readonly<{ children?: ReactNode; }> & Readonly<{}>'.

The component is imported like so:

import TestComponent from './TestComponent'

and called like so:

<TestComponent bar="hello"/>

Here is the structure of my component folder:

├── TestComponent.tsx
├── TestComponentContainer.ts
└── index.ts

index.ts is as follows:

import TestComponentContainer from './TestComponentContainer'

export default TestComponentContainer

TestComponent.jsx is as follows:

import * as React from 'react'

interface IProps {
  foo: string
  bar: string
}

const TestComponent: React.SFC<IProps> = ({ foo, bar }) => (
  <div>
    <p>{foo}</p>
    <p>{bar}</p>
  </div>
)

export default TestComponent

TestComponentContainer.ts is as follows:

import { compose, withProps } from 'recompose'

import TestComponent from './TestComponent'

export default compose(
  withProps({
    foo: 'stringy string string',
  }),
)(TestComponent)

I get that I'm missing some kind of type declaration for the props that are being passed through but can't for the life of me figure out exactly what I should do.

Edit:

Furthermore, how does one do this with the nested approach:

export default compose(
  setDisplayName('PlayerCardContainer'),
  withMutation(mutation),
  withHandlers(createHandlers),
)(PlayerCard)
bert
  • 3,989
  • 4
  • 16
  • 32

1 Answers1

3

The typing for compose is very generic and allows you to specify the type of the resulting component and the type of the component it can be called on but it doesn't ensure the type safety or compatibility of the functions handed to it.

For this reason it is best to avoid compose and simply nest the HOC calls:

import { withProps } from 'recompose';

import TestComponent from './TestComponent';

export default withProps({
  foo: 'stringy string string',
})(TestComponent);

Edit:

For the added code example nesting the HOC's instead of using compose would look like this:

export default setDisplayName('PlayerCardContainer')(
  withMutation(mutation)(
    withHandlers(createHandlers)(
      PlayerCard
    )
  )
);
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • 1
    nice string value, btw – Brian Adams Sep 17 '18 at 18:03
  • Haha thanks on both accounts, that seems to work! I guess I'm kinda losing the composability aspect of recompose though by doing this, aren't i? What if I want to use another HOC in there too like `withHandlers`? – bert Sep 17 '18 at 18:17
  • Sorry should have read you second link properly as it’s explained very well there! Thanks for your help – bert Sep 17 '18 at 18:36
  • Apologies again! I don't understand how you can use the nested method when each HOC takes it's own arguments. I've modified my question so you can see the formatted code of what I mean. – bert Sep 17 '18 at 19:02
  • 1
    no worries, the important thing to remember is that `setDisplayName('PlayerCardContainer')`, `withMutation(mutation)`, and `withHandlers(createHandlers)` all return *functions*. `compose` is just syntactic sugar for calling each on the result of calling the next. It turns the nested function calls into a nice-looking flat list, but that's all it does. If the typings improve it would be nice to keep the syntactic sugar, but until the typings improve it is safer to nest the functions so TypeScript knows exactly what is happening and can effectively enforce the static typing. – Brian Adams Sep 17 '18 at 19:52
  • 1
    Ahaa it’s just properly clicked for me thank you very much! – bert Sep 17 '18 at 19:53
  • Weirdly it works absolutely fine when I just have `withMutation` and `withHandlers` but as soon as I try to nest those within `setDisplayName` it breaks again with `Property 'bar' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & Readonly<{ children?: ReactNode; }> & Readonly<{}>'.` – bert Sep 17 '18 at 19:59
  • 1
    Huh. The [typing for `setDisplayName` uses a *type argument*](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/11ce407429b716983fc99c87a92f65f9c6133295/types/recompose/index.d.ts#L282-L284). I might open a pull request to change this, there is no reason why `setDisplayName` can't infer the type. In the meantime you can pass `{bar: string}` as the type argument like this: `setDisplayName<{bar: string}>(...` – Brian Adams Sep 17 '18 at 22:20
  • 1
    update: the [PR just merged](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/29114) so you can drop the type argument for `setDisplayName` with version 0.27.0 or higher of @types/recompose – Brian Adams Oct 04 '18 at 03:05