remix-run / react-router

Declarative routing for React
https://reactrouter.com
MIT License
52.82k stars 10.23k forks source link

Can't pass Components into react-router Routes with custom type arguments defined #4317

Closed fultonm closed 7 years ago

fultonm commented 7 years ago

Version

2.8.1

Test Case

Steps to reproduce

Use this code:

routes.tsx: (Note: I have modified @types/react-router to also export the type ReactComponents)

import * as React from 'react'
import { IndexRoute, Route, ReactComponents } from 'react-router'

import App from './App'
import MainComponent from './MainComponent'
import SidebarComponent from './SidebarComponent'

const components: ReactComponents = { // Type '{ main: typeof MainComponent; sidebar: typeof SidebarComponent; }' is not assignable to type 'RouteComponents'.
  main: MainComponent,
  sidebar: SidebarComponent
}

const routes = () => {
  return (
    <Route path="/" component={App}>
      <IndexRoute components={components} /> // No error here.
      <Route components={main: MainComponent, sidebar: SidebarComponent} /> // Same error as above
    </Route>
  )
}

export default routes

App.tsx

import * as React from 'react'

interface P { // Defining custom type argument
  main: React.Component<any, any>,
  sidebar: React.Component<any, any>
}

export default class App extends React.Component<P, null> { // using null is better than any when the Component does not use state
  constructor(props: P) {    
    super(props)
  }

  public render() {
    const { main, sidebar } = this.props

    return (
      <div>
        {sidebar}
        {main}
      </div>
    )
  }
}

MainComponent.tsx:

import * as React from 'react'

interface S { // Defining my Type Argument...
  query: string
}

export default class MainComponent extends React.Component<null, S> { // using null is better than any when the component does not use any props
  constructor(props: null) {
    super(props)
    this.handleQueryChange = this.handleQueryChange.bind(this)
    this.state = { query: '' }
  }

  public handleQueryChange(input: string) {
    this.setState({ query: input })
  }

  public render() {
    return (
      <div>Content</div>
    )
  }
}

SidebarComponent.tsx

import * as React from 'react'
import { Link } from 'react-router'

export default class SidebarComponent extends React.Component<null, null> {
  constructor(props: null) {
    super(props)
  }

  public render() {
    return (
      <div>
        <h3>Menu</h3>
        <ul role="nav">
          <li><Link to="">Something</Link></li>
          <li><Link to="/add">Else</Link></li>
        </ul>
      </div>
    )
  }
}

Expected Behavior

React.Component<P, S> accepts two custom type arguments for prop and state, so react-router/typescript should be able to handle any sort of type arguments without a problem. The code will work fine if I use React.Component<any, any>.

Actual Behavior

But, react-router Route only accepts Components that have type arguments like React.Component<any, any>. When using my custom defined prop and state interfaces in place of any, any, I get the following error:

Type '{ main: typeof MainComponent; sidebar: typeof SidebarComponent; }' is not assignable to type 'RouteComponents'. Property 'sidebar' is incompatible with index signature. Type 'typeof SidebarComponent' is not assignable to type 'ReactType'. Type 'typeof SidebarComponent' is not assignable to type 'StatelessComponent'. Type 'typeof SidebarComponent' provides no match for the signature '(props: any, context?: any): ReactElement'

These errors go away if I simply change my Components to extend from React.Component<any, any>

timdorr commented 7 years ago

Any TS typings are unofficial, so this isn't the place to report issues with them. None of us here are TS experts.