0

I'm trying to navigate between a master detail data format in React. The source page does this:

{myDataList.map(myData =>
    <tr key={myData.dataId}>
    <td>{myData.dataId}</td>
    <td>{myData.dataDescription}</td>
    <td>
        <NavLink to={'/details/' + myData.dataId}>
            Details
        </NavLink>
    </td>

Clicking the edit link does navigate to the details page with the correct URL; which does this (mainly taken from the example template in VS):

interface DetailsState {
    details?: DataDetails;
    loading: boolean;
}

export class Details extends React.Component<RouteComponentProps<number>, DetailsState> {
constructor() {
    super();
    this.state = { details: undefined, loading: true };

    console.log("match params: " + this.props.match.params);

    fetch('api/Data/Details/' + this.props.match.params)
        .then(response => response.json() as Promise<DataDetails>)
        .then(data => {
            this.setState({ details: data, loading: false });
        });
}

My problem is that the console.log like above shows:

TypeError: _this.props is undefined

And so I can't access the dataId that I'm passing through.

Clearly I'm passing (or receiving) this parameter incorrectly; so my question is: what is the correct way to pass the parameter? It's worth noting that my project is based on the VS Template, and I am using tsx, rather than jsx files.

Paul Michaels
  • 16,185
  • 43
  • 146
  • 269
  • Possible duplicate of [Diff between super(props) and super()](https://stackoverflow.com/questions/30571875/whats-the-difference-between-super-and-superprops-in-react-when-using-e?rq=1) – Mayank Shukla Mar 28 '18 at 16:25

2 Answers2

2

You cannot access this.props in a component's constructor. However, it is available as the first parameter. You can do this:

export class Details extends React.Component<RouteComponentProps<number>, DetailsState> {
  constructor(props) {
    super(props);
    this.state = { details: undefined, loading: true };

    console.log("match params: " + props.match.params);

    fetch('api/Data/Details/' + props.match.params)
      .then(response => response.json() as Promise<DataDetails>)
      .then(data => {
        this.setState({ details: data, loading: false });
      });
  }
}

Make sure that you call super(props) as the first thing in the constructor, or else React will yell at you.

EDIT:

As @Dan notes, it's generally not avisable to perform asynchronous actions in the constructor. Better to do that in componentDidMount():

export class Details extends React.Component<RouteComponentProps<number>, DetailsState> {
  constructor(props) {
    super(props);
    this.state = { details: undefined, loading: true };

    console.log("match params: " + props.match.params);
  }

  componentDidMount() {
    fetch('api/Data/Details/' + this.props.match.params)
      .then(response => response.json() as Promise<DataDetails>)
      .then(data => {
        this.setState({ details: data, loading: false });
      });
  }
}
ethan.roday
  • 2,485
  • 1
  • 23
  • 27
  • You shouldn't do network requests in the constructor. It's encouraged to do so in `componentDidMount()`. – Dan Mar 28 '18 at 16:28
  • Yes, that's definitely true. I was aiming to give the minimal edits possible to answer @pm_2's question. But I will add a clarification. – ethan.roday Mar 28 '18 at 16:30
  • Because it's typescript - it wants a type for `props`, but as far as I'm concerned - props is just a `number`? – Paul Michaels Mar 28 '18 at 16:30
  • Regarding the network request - that's straight from the MS template – Paul Michaels Mar 28 '18 at 16:31
  • There's nothing technically wrong with it AFAIK (though I might be wrong), since you're using `setState()` in the callback to the `fetch`, but it's just generally recommended to do any asynchronous things in `componentDidMount()`. – ethan.roday Mar 28 '18 at 16:33
  • I'm not sure what you mean about the types. If you don't care about the props, you can use `RouteComponentProps`, but you might as well type them correctly. If you're confused about what the types should look like, then that's a topic for a separate question. – ethan.roday Mar 28 '18 at 16:36
  • Thanks - I've defined the constructor `props` as RouteComponentProps, since that's what's being passed in; however `console.log("match: " + this.props.match.params);` just returns `object`. How do I extract the ID from that? – Paul Michaels Mar 28 '18 at 17:00
  • That depends on the definition of the `Route` that renders `Details`. If the `path` prop of that `Route` is `/details/:id` You should be able to access the `id` in `this.props.match.params.id`. But if you have other router-specific questions, that might be worth moving to a separate question. SO is warning me about how long this comments thread is getting. – ethan.roday Mar 28 '18 at 17:09
  • 1
    Thanks for your help - I managed to get this to work by defining an interface that contains a single property of ID – Paul Michaels Mar 28 '18 at 17:43
1

If you want to access props in your constructor, you need to pass them in:

constructor(props) {
    super(props);
}
jmargolisvt
  • 5,722
  • 4
  • 29
  • 46