0

I need to call this.getFact from the Animal component, but using this raises TypeError: this is undefined

import './App.css';
import React from 'react';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      fact: '',
    }
  }

  getFact(endpoint) {
    fetch(endpoint)
      .then(response => response.json())
      .then(data => {
        this.setState({
          fact: data.text,
        });
      })
  }

  Animal(props) {
    return (
      <div>
        <h1>{props.name}</h1>
        <button onClick={() => this.getFact(props.endpoint)}>get a {props.name.toLowerCase()} fact</button>
      </div>
    )
  }

  render() {
    return (
      <div className="App">
        <div>
          <p>{this.state.fact}</p>
        </div>
        <div className="animals">
          <this.Animal name="Dog" endpoint="https://some-random-api.ml/facts/dog" />
          <this.Animal name="Cat" endpoint="https://some-random-api.ml/facts/dog" />
          <this.Animal name="Bird" endpoint="https://some-random-api.ml/facts/dog" />
          <this.Animal name="Otter" endpoint="https://some-random-api.ml/facts/dog" />
        </div>
      </div>
    );
  }
}

export default App;

1
  • You don't have an Animal component. You have an Animal method on your App component. Create a new component with its own methods (or create a new functional component with hooks). Commented Jan 14, 2022 at 21:21

2 Answers 2

1

You need to bind both of the functions (getFact and Animal) to be able to use this inside the Animal function. In your constructor do this:

  constructor(props) {
    super(props);
    this.state = {
      fact: ""
    };

    // Here you will bind the functions so they can be callable with this
    this.getFact = this.getFact.bind(this);
    this.Animal = this.Animal.bind(this);
  }

That will solve the issue but I will suggest to move the Animal component outside of your class and pass the getFact as a prop. you still need to bind the getFact function, but honestly it will be more react like and in the long run is more maintainable. Something like this

function Animal(props) {
  return (
    <div>
      <h1>{props.name}</h1>
      <button onClick={() => props.getFact(props.endpoint)}>
        get a {props.name.toLowerCase()} fact
      </button>
    </div>
  );
}

class AppReactWay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      fact: ""
    };
    this.getFact = this.getFact.bind(this);
  }

// Same code as you have

  render() {
    return (
      <div className="App">
        <div>
          <p>{this.state.fact}</p>
        </div>
        <div className="animals">
          <Animal
            name="Dog"
            getFact={this.getFact}
            endpoint="https://some-random-api.ml/facts/dog"
          />
        </div>
      </div>
    );
  }
}

export default App;

Also after checking the API response you need to modify the getFact function, is not data.text, it is data.fact.

  getFact(endpoint) {
    fetch(endpoint)
      .then((response) => response.json())
      .then(({ fact }) => {
        this.setState({
          fact
        });
      });
  }

Here's a working sandbox with both examples.

Sign up to request clarification or add additional context in comments.

Comments

0

The better approach would be to directly call {this.Animal({ name: 'Dog', endpoint: 'https://some-random-api.ml/facts/dog'})} and so on for every animal.

The best approach would be for Animal to have it's own component and pass getFact function as param, don't forget you will need to bind it in that case or it will loose this context.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.