JoaoTMDias
JoaoTMDias

Reputation: 43

How do I create a High-Order React Component connected to Redux using Typescript?

I need to create a React High-Order component to protect routes in my app against users that don't have an access token. Then using that HOC wrap a Component like this on parent component:

<Route
    exact
    path={INDEX_URL}
    render={isAuthenticated((props: any) => (<LandingPage {...props} />))}
/>

I'm using Typescript and I'm having some issues with the connect function and withRouter from react-router-dom.

Can someone help me, please? Thank you and have a great weekend! 😀

I've already tried to extend my interface with RouterProps and/or withRouter but none worked. It seems that is some typing related issue, but I can't figure out exactly what it is.

import * as React from 'react';
import { Redirect, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import * as H from 'history';

// Constants
import { LOGIN_URL } from '../../../data/constants/index.constants';

interface IAuthenticatedProps {
    accessToken: string | null;
    location: H.Location;
}

interface IAuthenticatedState {
    isAuthenticated: boolean | undefined;
}

/**
 * @description Higher-order component (HOC) to wrap Authenticated pages
 * @date 2019-01-18
 * @class Authenticated
 * @extends {React.Component<IAuthenticatedProps, any>}
 */
const isAuthenticated = (ComposedComponent: any) => {
    class Authenticated extends React.Component<
        IAuthenticatedProps,
        IAuthenticatedState
    > {
(...)
    function mapStateToProps(state: any) {
        return {
            accessToken: state.authentication.accessToken,
        };
    }

    return withRouter(connect(mapStateToProps)(Authenticated));
};

export default isAuthenticated;

I expected no issues, but Typescript is giving me this:

[ts]
Argument of type 'ConnectedComponentClass<typeof Authenticated, Pick<IAuthenticatedProps, "location">>' is not assignable to parameter of type 'ComponentType<RouteComponentProps<any, StaticContext, any>>'.
  Type 'ConnectedComponentClass<typeof Authenticated, Pick<IAuthenticatedProps, "location">>' is not assignable to type 'ComponentClass<RouteComponentProps<any, StaticContext, any>, any>'.
    Type 'Component<Pick<IAuthenticatedProps, "location">, any, any>' is not assignable to type 'Component<RouteComponentProps<any, StaticContext, any>, any, any>'.
      Types of property 'props' are incompatible.
        Type 'Readonly<{ children?: ReactNode; }> & Readonly<Pick<IAuthenticatedProps, "location">>' is not assignable to type 'Readonly<{ children?: ReactNode; }> & Readonly<RouteComponentProps<any, StaticContext, any>>'.
          Type 'Readonly<{ children?: ReactNode; }> & Readonly<Pick<IAuthenticatedProps, "location">>' is missing the following properties from type 'Readonly<RouteComponentProps<any, StaticContext, any>>': history, match [2345]

Upvotes: 4

Views: 1662

Answers (1)

ethan.roday
ethan.roday

Reputation: 2635

withRouter() has the effect of providing props to the component that it wraps. Therefore, in order to use it, the component you want to wrap has to expect those props. In this case, Authenticated is only expecting IAuthenticatedProps, but withRouter() provides many more. So TypeScript is complaining because withRouter() is trying to provide props to Authenticated that Authenticated doesn't say it expects.

The props that withRouter() provides are defined in the interface RouteComponentProps. Try adding that to your prop type for Authenticated:

  class Authenticated extends React.Component<
    IAuthenticatedProps & RouteComponentProps<{}>,
    IAuthenticatedState
  > {
    // ...
  }

You can also remove location from your IAuthenticatedProps interface as that is also included in the RouteComponentProps interface.

One more problem is that the render prop in Route expects a function, not any old React component type. Since isAuthenticated() returns a React.ComponentClass, you'll need to do something like the following:

const Authenticated = isAuthenticated(LandingPage);
// ...
<Route
  exact
  path={INDEX_URL}
  render={(props: any) => <Authenticated {...props} />}
/>

Upvotes: 3

Related Questions