简体   繁体   中英

Display data from an API when clicking a button. Using ReactJS

I am trying to display the data of each character when I click the Info Button. I know it is because in the onButtonClickHandler function it can not see the state. I have also tried this.state.person but it gives me an error saying "can not read state". And if I try just state.person it will give me "undefined".

What is the best way to do that? Thank you

API Link: https://swapi.dev/people/

import React from "react";

export default class FetchActors extends React.Component {
  state = {
    loading: true,
    person: null
  };

  async componentDidMount() {
    const url = "https://swapi.dev/api/people/";
    const response = await fetch(url);
    const data = await response.json();
    this.setState({ person: data.results, loading: false });
  }

  render() {
    if (this.state.loading) {
      return <div>loading...</div>;
    }

    if (!this.state.person.length) {
      return <div>didn't get a person</div>;
    }

   function onButtonClickHandler(state) {
      console.log(state.person);
    };


    return (
      <div>
        <h1>Actors</h1>
        {this.state.person.map(person =>(
        <div>
          <div>
            {person.name}
            <button onClick={onButtonClickHandler}>Info</button>

          </div>

        </div>
        ))}
        <button onClick={onButtonClickHandler}>Enter</button>
      </div>
    );
  }
}

Please correct me if I'm wrong

The most likely reason why you are seeing this is because of the way javascript internally works. The syntax:

function xyz() {

}

has an implicit this

Maybe try changing your code from:

function onButtonClickHandler(state) {
  console.log(state.person);
};

to:

const onButtonClickHandler = () => {
  console.log(this.state.person);
};

Further Reading: Here

there are a few ways you can resolve your issue; I'll give you the more common approach.

  1. You want to define your click handler as a class (instance) method, rather than declare it as a function inside the render method (you can define it as a function inside the render method, but that's probably not the best way to do it for a variety of reasons that are out of scope).
  2. You will also have to bind it's 'this' value to the class (instance) because click handlers are triggered asynchronously.
  3. Finally, add a button and trigger the fetch on click:
class Actors extends React.Component {
  state = {
    loading: false,
    actors: undefined,
  };

  constructor(props) {
    super(props);
    this.fetchActors = this.fetchActors.bind(this);
  }

  async fetchActors() {
    this.setState({ loading: true });
    const url = "https://swapi.dev/api/people/";
    const response = await fetch(url);
    const data = await response.json();
    this.setState({ actors: data.results, loading: false });
  }

  render() {
    console.log('Actors: ', this.state.actors);
    return <button onClick={this.fetchActors}>fetch actors</button>;
  }
}

You have defined your function onButtonClickHandler as a function that takes one argument, and logs the person property of that argument. The argument state in your function has nothing to do with the state of your component. As javascript sees it, they are two totally unrelated variables which just happen to have the same name.

function onButtonClickHandler(state) {
   console.log(state.person);
};

When button calls onClick , it passes the event as the argument. So your onButtonClickHandler is logging the person property of the event, which obviously doesn't exist.

Since you are not using any information from the event, your function should take no arguments. As others have said, you should also move this function outside of the render() method so that it is not recreated on each render. The suggestion to use bind is not necessary if you use an arrow function, since these bind automatically.

export default class FetchActors extends React.Component {
  /*...*/

  onButtonClickHandler = () => {
     console.log(this.state.person);
  };

}

Inside render()

<button onClick={this.onButtonClickHandler}>Enter</button>

You could also define the function inline, as an arrow function which takes no arguments:

<button onClick={() => console.log(this.state.person)}>Enter</button>

If you are new to react, I recommend learning with function components rather than class components.

Edit:

Updating this answer regarding our comments. I was so caught up in explaining the errors from doing the wrong thing that I neglected to explain how to do the right thing!

I am trying to display the data of each character when I click the Info Button.

Once we call the API, we already have the info loaded for each character. We just need to know which one we want to display. You can add a property expanded to your state and use it to store the index (or id or name , whatever you want really) of the currently expanded item.

When we loop through to show the name and info button, we check if that character is the expanded one. If so, we show the character info.

Now the onClick handler of our button is responsible for setting state.expanded to the character that we clicked it from.

{this.state.person.map((person, i) =>(
<div>
  <div>
    {person.name}
    <button onClick={() => this.setState({expanded: i})}>Info</button>
    {this.state.expanded === i && (
      <CharacterInfo
        key={person.name}
        person={person}
      />
    )}
  </div>

CodeSandbox Link

Sometimes i takes react a min to load the updated state.

import React from "react";

export default class FetchActors extends React.Component {
  state = {
    loading: true,
    person: null
  };

  async componentDidMount() {
    const url = "https://swapi.dev/api/people/";
    const response = await fetch(url);
    const data = await response.json();
    if(!data.results) { // throw error }
    this.setState({ person: data.results, loading: false }, () => {
  console.log(this.state.person) // log out your data to verify
});
  }

  render() {
    if (this.state.loading || !this.state.person) { // wait for person data
      return <div>loading...</div>;
    }else{


   function onButtonClickHandler(state) { // just make a componentDidUpdate function
      console.log(state.person);
    };


    return (
      <div>
        <h1>Actors</h1>
        {this.state.person.map(person =>(
        <div>
          <div>
            {person.name}
            <button onClick={onButtonClickHandler}>Info</button>

          </div>

        </div>
        ))}
        <button onClick={onButtonClickHandler}>Enter</button>
      </div>
    );
  }
}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM