TypeScript & React: compose higher-order components

2.1k Views Asked by At

I'm new to TypeScript with React and I have (already) two HOCs in React and I would like to compose them because it is likely that there will be more.

import { getIsAuthenticated } from 'features/user-authentication/user-authentication-reducer';
import { connect, ConnectedProps } from 'react-redux';
import { RootState } from 'redux/root-reducer';

import { withTranslation } from '../../../i18n';
import HomePageComponent from './home-page-component';

const mapStateToProps = (state: RootState) => ({
  isAuthenticated: getIsAuthenticated(state),
});

const connector = connect(mapStateToProps);

export type PropsFromRedux = ConnectedProps<typeof connector>;

export default withTranslation()(connector(HomePageComponent));

That is how it currently looks (and works / compiles). The component is:

// ...
import { TFunction } from 'next-i18next';
// ...
import { PropsFromRedux } from './home-page-container';

type Props = {
  readonly t: TFunction;
} & PropsFromRedux;

const HomePageComponent = ({ isAuthenticated, t }: Props) => (
// ...

In JavaScript I usually do:

export default compose(withTranslation(), connector)(HomePageComponent);

where compose is either from Ramda or from Redux itself.

Using Ramda's compose throws two errors.

  1. connector: No overload matches this call.
  2. HomePageComponent: Expected 0 arguments, but got 1.

Using Redux' compose compiles here, but breaks my tests.

import { render, screen } from 'tests/test-helpers';

import HomePageContainer from './home-page-container';

describe('Home Page Container', () => {
  it('should render a greeting', () => {
    render(<HomePageContainer />);

    expect(screen.getByText(/hello world/i)).toBeInTheDocument();
  });
});

In the test file, HomePageContainer throws:

JSX element type 'HomePageContainer' does not have any construct or call signatures.

How can I use compose for higher-order components and get it to work with TypeScript?

PS: I found this other question and answer in SO, but the answer doesn't work in the latest TS versions and its only for two HOCs, I need to compose more.

My tsconfig.json:

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "es5"
  },
  "exclude": ["node_modules"],
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

Minimal example in CodeSandbox: https://codesandbox.io/s/hoc-breaking-example-mtp3m

1

There are 1 best solutions below

0
On BEST ANSWER

If you go to rambda compose typing you'll see that it's generic, so you can define what would be the resulting function, typing it like this removes the error

export default compose<React.FunctionComponent>(
  withTitle("Hello StackOverflow!"),
  connector
)(App);