Reusable layout component in TypeScript React: Property 'path' does not exist on type 'IntrinsicAttributes & IProps'

Viewed 1463

I'm writing a reusable, full-width layout component using TypeScript React with React Router v5. I would like this component to be able to be applied to multiple unrelated pages on my site.

I've tried creating a custom Route for my layout component whose props extend RouteComponentProps and also Route but I get the following error in both cases:

Property 'path' does not exist on type 'IntrinsicAttributes & IProps'. TS2322

I guess this is because TypeScript doesn't recognize that my custom Route isn't like the one that React Router uses and thus it can't find the path prop.

How do I get my layout component to be used as a Route?

full-width-layout.tsx:

import React, { Component, ComponentType } from 'react';
import { Route, RouteComponentProps } from 'react-router-dom';

export const FullWidthLayout = ({ children }: any) => (
    <div>
        {children}
    </div>
)

interface IProps extends RouteComponentProps {
    component: ComponentType
}

export const FullWidthLayoutRoute = ({ component, ...rest }: IProps) => {
    return (
        <Route {...rest} render={matchProps => (
            <FullWidthLayout>
                <Component {...matchProps} />
            </FullWidthLayout>
        )} />
    )
}

export default FullWidthLayout;

App.tsx:

import React, { Component } from "react";
import { Switch } from "react-router-dom";
import "./App.scss";
import Breadcrumbs from "./breadcrumbs/breadcrumbs";
import Footer from "./footer/footer";
import { MaintainCart } from "./helpers/maintain-cart";
import Home from "./home";
import { FullWidthLayoutRoute } from "./layouts/full-width-layout";
import Menu from "./menu";
import { CartProvider } from "./shopping-cart/shopping-cart-context";


class App extends Component {
    render() {
        return (
            <CartProvider>
                <div>
                    <Menu />

                    <Breadcrumbs />

                    <MaintainCart />

                    <Switch>
                        <FullWidthLayoutRoute path="/" component={Home} />
                    </Switch>

                    <Footer />
                </div>
            </CartProvider>
        );
    }
}

export default App;

index.tsx:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "./main.css";
import "./bootstrap.min.css";
import "./responsive.css";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import * as serviceWorker from "./serviceWorker";
import * as dotenv from 'dotenv'
import ScrollToTop from "./common/scroll-to-top";


dotenv.config()

ReactDOM.render(
  <Router basename='/rap'>
    <ScrollToTop />
    <App />
  </Router>,
  document.getElementById("root")
);

serviceWorker.unregister();
1 Answers

The issue is from extending wrong interface RouteComponentProps. The right one is supposed to be RouteProps so that your code should be refined as following:

import React, { Component, ComponentType } from 'react';
import { Route, RouteProps } from 'react-router-dom';

export const FullWidthLayout = ({ children }: any) => (
  // ...
)

// Replace with `RouteProps`
interface IProps extends RouteProps {
    // should also specify for props here
    // if you have no idea how just leave it as any
    component: ComponentType<any>
}

// Looks like you're using a base component class `Component` in the original code
// so I replace `component` as `Comp` for you
export const FullWidthLayoutRoute = ({ component: Comp, ...rest }: IProps) => {
    return (
        <Route {...rest} render={matchProps => (
            <FullWidthLayout>
                <Comp {...matchProps} />
            </FullWidthLayout>
        )} />
    )
}
Related