Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

issue with types when using "withRouter" and Typescript

I'm trying to get some more deep knowledge and practice into React+Typescript, and I've come across this typing error when using withRouter from react-router-dom.

My code snippet is very simple, and I've tried finding out people with the same issue, and some of the answers pointed to an error with an upgrade (but they were from 2016, so...) and some of them were using a connect() statement which I am not making use of (which lead to the question, "Am I doing this wrong because of not using it?"). I see that some of those suggestions also involved mapping Props to State, which I haven't done (nor seen) until now. I hope someone has some suggestions on what am I missing and what else I should be looking at.

The code is:

import React from "react";
import { withRouter } from "react-router-dom";

interface ISection {
  id: number;
  title: string;
  imageUrl: string;
  size: string;
}

class MenuItem extends React.Component<ISection> {
  render() {
    return (
      <div className={`${this.props.size} menu-item`}>
        <div
          className="background-image"
          style={{ backgroundImage: `url(${this.props.imageUrl})` }}
        />
        <div className="content">
          <h1 className="title">{this.props.title}</h1>
          <span className="subtitle">some subtitle</span>
        </div>
      </div>
    );
  }
}

export default withRouter(MenuItem);

What I would be expecting from here is to work smoothly (I have to say that I tried first with a functional component since I don't have any state, but all the solutions I saw involved a class component, so I moved it into it), but instead I'm getting the following error on the MenuItem in the last line:

Argument of type 'typeof MenuItem' 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 MenuItem' is not assignable to type 'ComponentClass<RouteComponentProps<any, StaticContext, any>, any>'.
    Types of parameters 'props' and 'props' are incompatible.
      Type 'RouteComponentProps<any, StaticContext, any>' is missing the following properties from type 'Readonly<ISection>': id, title, imageUrl, sizets(2345)

My questions are:

  1. Why does it say "type 'typeof MenuItem'"? Shouldn't it just say the type of 'MenuItem' instead of the function to obtain the type?

  2. Is it necessary for withRouter to work with class components, or does it also work on functional components?

  3. Do I need to connect() something, or map Props onto State? If so, why?

  4. And lastly, how can I fix this?

like image 508
P.Gracia Avatar asked Jul 10 '19 21:07

P.Gracia


People also ask

Why withRouter is not working?

The error "export 'withRouter' (imported as 'withRouter') was not found in 'react-router-dom'" occurs because the withRouter function has been removed in react router v6. To solve the error, install version 5.2. 0 of react router by running npm install [email protected] .

Is withRouter outdated?

The library-provided HOC, withRouter, has been deprecated in React Router v6. If you need to use v6 and are using class-based React components, then you will need to write your own HOC which wraps the v6 use* hooks.

What is withRouter used for?

withRouter is a higher order component that will pass closest route's match , current location , and history props to the wrapped component whenever it renders. simply it connects component to the router. Not all components, especially the shared components, will have the access to such router's props.

What is RouteComponentProps?

RouteComponentProps looks to be a Typescript interface definition of react-router-dom's route-props. The RouteComponentProps prop-types definition may've been part of react-router-dom but isn't currently exported. I found the Typescript export in Definitely Typed.


1 Answers

As of documentation, withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.

So MenuItem component should have props to receive them. For now, MenuItem component has props of type ISection which does not include router props.

The easiest way to add router props is to intersect ISection with RouteComponentProps.

import { withRouter, RouteComponentProps } from "react-router-dom";

// ...
class MenuItem extends React.Component<ISection & RouteComponentProps> {

Full code is

import * as React from 'react';
import { withRouter, RouteComponentProps } from "react-router-dom";

interface ISection {
    id: number;
    title: string;
    imageUrl: string;
    size: string;
}

class MenuItem extends React.Component<ISection & RouteComponentProps> {
    render() {
        return (
            <div className={`${this.props.size} menu-item`}>
                <div
                    className="background-image"
                    style={{ backgroundImage: `url(${this.props.imageUrl})` }}
                />
                <div className="content">
                    <h1 className="title">{this.props.title}</h1>
                    <span className="subtitle">some subtitle</span>
                </div>
            </div>
        );
    }
}

export default withRouter(MenuItem);

And answers to your questions

  1. Why does it say "type 'typeof MenuItem'"? Shouldn't it just say the type of 'MenuItem' instead of the function to obtain the type?

    Error arisen from types incompatibility. MenuItem is class, not type. To get type of MenuItem you should use typeof MenuItem. So typeof MenuItem is type. And compiler says correctly, "type typeof MenuItem".

  2. Is it necessary for withRouter to work with class components, or does it also work on functional components?

    It is allowed to work with class component and with functional component.

    This is how your component will look like if implemented as functional

    const Cmp1: React.FunctionComponent<ISection & RouteComponentProps> = (props) => {
        return (
            <div className={`${props.size} menu-item`}>
                <div
                    className="background-image"
                    style={{ backgroundImage: `url(${props.imageUrl})` }}
                />
                <div className="content">
                    <h1 className="title">{props.title}</h1>
                    <span className="subtitle">some subtitle</span>
                </div>
            </div>
        );
    }
    
    const WrappedCmp = withRouter(Cmp1);
    
  3. Do I need to connect() something, or map Props onto State? If so, why?

    No, this is not strict requirement. connect is part of Redux, so if you use Redux you may connect. Here is documentation of how to use withRouter with connect. But again, it is not required.

  4. And lastly, how can I fix this?

    Already answered. See above :-)

like image 50
Fyodor Avatar answered Sep 18 '22 23:09

Fyodor