Type Issues When Using React with TypeScript

· 5 min read

Frontend projects can improve code robustness with TypeScript support. Of course, during usage, you’ll inevitably encounter many type definition issues. Here’s a summary.

To pursue quick solutions, we might lazily use // @ts-ignore, but the downside of this approach is giving up type safety. So, avoid it if possible - it’s better to thoroughly solve the problem.

Using children in Function Components

For stateless components, we often use function components for declaration, and frequently use children for object transparency.

import React from 'react';

interface IProps {
  permission: boolean;
}

const AuthShow = ({ permission, children }: React.PropsWithChildren<IProps>) => {
  return <>{isPermit() ? children : null}</>;
};

export default AuthShow;

Redux Store Configuration Error

Error:(26, 21) TS2741: Property ‘[Symbol.observable]’ is missing in type ‘Store<IRootState, AnyAction> & { dispatch: {}; }’ but required in type ‘Store<any, AnyAction>’.


 <Provider store={store}>
            <Component />
          </Provider>

Solution: update redux from 4.0.0 to 4.0.3

Related GitHub issue discussion

Type Error When Using withRouter

Error:(18, 27) TS2345: Argument of type ’typeof Hello’ is not assignable to parameter of type ‘ComponentClass<RouteComponentProps<any, StaticContext, any>, any> | FunctionComponent<RouteComponentProps<any, StaticContext, any» | (FunctionComponent<RouteComponentProps<any, StaticContext, any» & ComponentClass<…>) | (ComponentClass<…> & FunctionComponent<…>)’. Type ’typeof Hello’ is not assignable to type ‘ComponentClass<RouteComponentProps<any, StaticContext, any>, any>’. Types of parameters ‘props’ and ‘props’ are incompatible. Property ’name’ is missing in type ‘RouteComponentProps<any, StaticContext, any>’ but required in type ‘Readonly’.

There are two easily confused route interface types: RouteComponentProps and RouteProps, but pay attention to their usage. When we use the withRouter higher-order component, the corresponding component’s props should inherit from RouteComponentProps, not RouteProps!

interface IProps extends RouteComponentProps {
  name: string;
}

export default withRouter(component);

When to Use RouteProps

For route definitions, for example:

const routesConfig: RouteProps[] = [
    {
      path: '/hello',
      component: HelloPage 
    }]

Antd Form and Redux Integration

In individual components, you might encounter situations where antd form and redux need to be integrated. Below is the correct usage order, note that connect is on the outermost layer.


export interface IProps extends DispatchProps, StateProps, FormComponentProps {
  name: string;
}

const component = Form.create<IProps>()(Hello);
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(component);

ref

Ref is used for component communication. Looking at online articles, you’ll find that sometimes ref is used, sometimes wrappedComponentRef is used, and you’ll encounter some pitfalls.

A Few Details

  • ref is an official React attribute, wrappedComponentRef is from antd
  • Redux’s connect higher-order component doesn’t expose ref objects by default. If you need to use them, you need to configure forwardRef
  • Because we need to configure the fourth parameter of connect, the third one must be configured. As defined, it’s a function, so we need the following syntax, otherwise type errors will still occur

Example

Below is a component wrapped by connect. If we need to use this component’s ref:

export default connect(
  null,
  mapDispatchToProps,
  () => mapDispatchToProps,
  {
    forwardRef: true
  }
)(SList);
 <SList ref={this.solutionQuoteDiscountRef}
          />

The above is the correct configuration method. If forwardRef is not configured, this.solutionQuoteDiscountRef.current will actually get the connect component, so you can’t directly call a specific method of the component.

When to Use wrappedComponentRef?

As mentioned above, when antd form wraps a component, you can use it. This is also the officially recommended approach.

Using ref to Access Form Values in Antd Button and Other UI Components

Sometimes you need to use ref to directly get the current values of text boxes and input fields. Usage is as follows:

...
  textAreaRef = React.createRef<TextArea>();

...
        <TextArea rows={5} ref={this.textAreaRef} cols={8} />
...

// Get value
this.textAreaRef.current.textAreaRef.value

Getting the value as shown above works, but causes syntax errors in TS. The current solution is to ignore it, but this isn’t elegant.

Error:(125, 91) TS2341: Property ’textAreaRef’ is private and only accessible within class ‘TextArea’.

Error When Using debounce in lodash

...
   this.doSearch = _.debounce(this.doSearch, 700);
...
   this.doSearch.cancel();

Using it as shown above will cause the following type error:

Error:(216, 21) TS2339: Property ‘cancel’ does not exist on type ‘() => Promise’.

Solution:

  debouncedDoSearch: Function & _.Cancelable;
  ...
  this.debouncedDoSearch = _.debounce(this.doSearch, 700);

 this.debouncedDoSearch.cancel();

 this.debouncedDoSearch();
   

Error When Using withRouter and injectIntl Together

Error:(188, 14) TS2345: Argument of type ‘ComponentClass<Pick<any, string | number | symbol>, any> & { WrappedComponent: ComponentType; }’ is not assignable to parameter of type ‘ComponentClass<RouteComponentProps<any, StaticContext, any>, any> | FunctionComponent<RouteComponentProps<any, StaticContext, any» | (FunctionComponent<RouteComponentProps<any, StaticContext, any» & ComponentClass<…>) | (ComponentClass<…> & FunctionComponent<…>)’. Type ‘ComponentClass<Pick<any, string | number | symbol>, any> & { WrappedComponent: ComponentType; }’ is not assignable to type ‘ComponentClass<RouteComponentProps<any, StaticContext, any>, any>’. Type ‘Component<Pick<any, string | number | symbol>, any, any>’ is not assignable to type ‘Component<RouteComponentProps<any, StaticContext, any>, any, any>’. Type ‘Pick<any, string | number | symbol>’ is missing the following properties from type ‘RouteComponentProps<any, StaticContext, any>’: history, location, match

export default connect(
 mapStateToProps,
 mapDispatchToProps
)(withRouter(injectIntl(BDetailPage)));

Solution

Add type inference: injectIntl

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(withRouter(injectIntl<IProps>(BDetailPage)));

Final Thoughts

  1. Both // @ts-ignore and any are like cheating - use them as little as possible. TypeScript’s purpose lies in types and assertions. If you directly cheat, the static analysis itself can’t function properly. This could then lead to serious bugs. So please respect types and understand the importance of explicit typing.
  2. TS syntax analysis errors differ slightly from TSLint errors. Lint errors directly tell you which rule was violated, and you just need to fix it accordingly. But TS syntax errors can sometimes be very long and intimidating, but if you read them carefully and parse them correctly, they’re still quite solvable. So don’t give up easily.