14

I am trying to type the props of my component and use an URL param at the same time. I get the following error:

Property 'match' does not exist on type 'Readonly<{children?:ReactNode}> & Readonly'

Here is some of my code:

import Api from '../../api/Api';

interface MyProps {
    api: Api
}

interface MyState {
    someString: string,
    loading: boolean
}

export class MyComponent extends React.Component<MyProps, MyState> {

    constructor(props: MyProps) {
        super(props);
        this.state = {
            someString: this.props.match.params.someString,//<-- Error is here on this line
            loading: true
        }
    }

    componentDidMount() {
        this.props.api.getSomeInfo(this.state.someString, this.callback)
    }

    callback() {
        let interval = setInterval(function () {
            this.setState({loading: false});
            clearInterval(interval)
        }, 3000);
    }

    render() {
        return (
            <div>
                <p>{this.someString}</p>
            </div>
        );
    }
}

As you can see all I am trying to do is:

1- Go to:

http://localhost:8080/someStrings/:someString

2- Grab the value of :someString in my component's constructor and store in state

3- Use the value of someString in my state to be able to pass it as an argument to my API module to do stuff

4- When the callback is executed in the API I remove the loading animation

My question is basically, how do I declare my MyProps to be able to acheive this?

Sebastien
  • 1,308
  • 2
  • 15
  • 39
  • Does this answer your question? [Property 'value' does not exist on type 'Readonly<{}>'](https://stackoverflow.com/questions/47561848/property-value-does-not-exist-on-type-readonly) – Michael Freidgeim Aug 03 '20 at 23:03

11 Answers11

13

This is an open issue in type definition. https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355

Workaround

import { RouteProps } from 'react-router';
import React from 'react';

interface MyProps {
    api: Api
}

interface MyState {
    someString: string,
    loading: boolean
}

class MyComponent extends React.Component<Props & RouteProps, State> // Pay attention here.
{
  // your code...
}

ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355#issuecomment-336022780

Community
  • 1
  • 1
Ritwick Dey
  • 18,464
  • 3
  • 24
  • 37
6

This is how I solved it

import {RouteComponentProps} from 'react-router';
interface IMyProps {}

interface IReactRouterParams {
  roomName: string;
  username: string;
}
export class MyComponent extends React.Component<
  IMyProps & RouteComponentProps<IReactRouterParams> {
 
  constructor(props: any) {
    super(props);
    //everything works here
    const {roomName, username} = this.props.match.params;
  }
}
David Buck
  • 3,752
  • 35
  • 31
  • 35
makkusu
  • 61
  • 1
  • 2
4

Try to add RouteComponentProps inteface to your props. Change:

export class MyComponent extends React.Component<MyProps, MyState> {

to

export class MyComponent extends React.Component<MyProps & RouteComponentProps, MyState> {
Anton Pegov
  • 1,413
  • 2
  • 20
  • 33
  • 1
    FYI people having this problem, until the types are sorted (mentioned in the accepted answer) this was the only solution that worked for me using react 16.6 and typescript 3.1.3 – Phil Gibbins Nov 09 '18 at 16:35
1

This is my script to handle the match problem, I believe you can borrow something from here.

class MyComponent extends React.Component<Props, State> {
  private params: any;

  constructor(props: any) {
    super(props);
    this.state = {
      paramId: null
    };
  }

  componentDidMount = () => {
    this.getParams();
  };

  getParams = () => {
    this.params = this.props;

    this.setState({
      paramId: this.params.match.params.id
    });
  };

  render() {
    const { paramId } = this.state;

    return (
      <React.Fragment></React.Fragment>
    );
  }
}

export default MyComponent;

Kidali Kevin
  • 39
  • 1
  • 1
1

I adapted the answer of Ritwick Dey into something I find a bit better.

Here it is:

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

interface Props {
    // your custom props go here
    api: Api
}

interface State {
    someString: string,
    isLoading: boolean
}

class MyComponent extends Component<RouteComponentProps<Props>, State>
{
  // your code...
}

OR

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

interface Props
    extends RouteComponentProps <{
        // your custom props go here
        api: Api
}> {}

interface State {
    someString: string,
    isLoading: boolean
}

class MyComponent extends Component<Props, State>
{
  // your code...
}

They are the same thing. It depends on your preference.

Marco Castanho
  • 395
  • 1
  • 7
  • 24
0

I had a similar issue and that was due to the App.test.jsx file. There, there was already a test case(below code) that I had totally forgotten about and none of the props was utilized in that test case scenario.

When I introduced the props to the test case, it worked. So in nature test was definitely guiding to the right direction since it was trying to use props that do not exists. But after updating the test case with my default props, it works.

Hopefully this helps.

it('renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render(<App **propName1={propwasnotinthetest} propName2={propwasnotinthetest}**/>, div); ReactDOM.unmountComponentAtNode(div); });

0

I experienced a similar issue and found the least type breaking fix is to locally cast the params as any.

someString: (this.props.match.params as any).someString
delesseps
  • 27
  • 3
0
    import * as React from 'react';
    import { RouteComponentProps } from 'react-router';

    interface RouteComponetPath {
        path?: string
    }

    interface ArticleContainerProps {
        another: number
    }

    interface ArticleContainerState {
        path?: string;
    }

    class ArticleContainer extends React.Component<ArticleContainerProps | RouteComponentProps<RouteComponetPath>, ArticleContainerState> {
        constructor(props: ArticleContainerProps | RouteComponentProps<RouteComponetPath>) {
            super(props);
            this.state = {
                path: (this.props as RouteComponentProps<RouteComponetPath>).match.params.path
            };
        }

        componentDidMount() {
            console.log("mount! Path is: ", this.state.path);
        }

        render() {
            return (
                <h1>This is a page with path {this.state.path} </h1>
            )
        }
    }

    export default ArticleContainer;

Which actually makes sense as you can have one interface to handle the paths and use a Union type and a Type Gueard as per TS documentation https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types

So, no hacks and remains strongly typed (not using any any anywhere).

Now I don't see any reason to pass both the path and another type of params, but you know... just to prove it can be done.

-1

RouteComponentProps should help in typescript

import { RouteComponentProps } from 'react-router';

export class Edit extends React.Component<MyProps & RouteComponentProps, MyState> {
  constructor(props: MyProps & RouteComponentProps) {
    super(props);

    ...
}
...
(this.props.match.params as any).someString
Prashanth Sams
  • 19,677
  • 20
  • 102
  • 125
-1

or because you are using this.props.match but not adding match to the MyProps interface

Maysam Torabi
  • 3,672
  • 2
  • 28
  • 31
-1

Easy Workaround would be:

Home extends React.Component<any, any>
Pritam Rajput
  • 112
  • 1
  • 6