Type Issues When Using React with TypeScript
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
- Both
// @ts-ignore
andany
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. - 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.