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

时间:2019-01-18 19:00:24

标签: javascript reactjs typescript redux react-router

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]

1 个答案:

答案 0 :(得分:1)

withRouter()的作用是为其包装的组件提供道具。因此,要使用它,要包装的组件必须期望。在这种情况下,Authenticated仅期望IAuthenticatedProps,但是withRouter()提供了更多。所以TypeScript抱怨是因为withRouter()试图向Authenticated提供Authenticated没有说期望的道具。

withRouter()提供的道具在界面RouteComponentProps中定义。尝试将其添加到Authenticated的道具类型中:

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

您还可以从location界面中删除IAuthenticatedProps,因为它也包含在RouteComponentProps界面中。

另一个问题是render中的Route属性需要一个函数,而不是任何旧的React组件类型。由于isAuthenticated()返回了React.ComponentClass,因此您需要执行以下操作:

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