ukka
ukka

Reputation: 11

React -- Have a list, but can't do anything with it

I am working on a React portfolio composed of several pages, a home, profile, project list, and contact page. For this part of the portfolio, it is intended to display an array of my Github repositories.

I'm having issues interacting with the array -- it's behaving strangely, and I've been stuck on this for days. I can print the array in the state from the console, but when I try to do anything with it, it behaves as if it is empty.

For example (tests placed in render()):

console.log(this.state.repos); //Prints out array of repos, as expected

console.log(`Repos: ${this.state.repos)}`); //Prints string, no array

What's really vexing is that the expected results seem to appear on unmounting -- when I make a small edit to RepoTable or Repo and save the source file, when it reloads on localhost the table appears, and the list I expect to see logs on the console. When I hit refresh, everything disappears, and the table also displays blank when I deploy it to test on Heroku.

I've tried a few different methods to try to fix this, but I keep circling back to this same issue. Here are some of the things I tried:

  1. Writing an alternate function using a for-loop to iterate the array in this.state -- I was able to log the state, but when trying to access elements in the for-loop, the loop behaved as if the array was empty.
  2. Using the map and alternate function in the constructor, as well as other functions such as componentDidMount (I think this is now outdated, but I tried it anyway), componentWillMount, among others.
  3. I tried building the list of Repo components in the constructor and then using it in render(), but it didn't work, I still got an empty list.

I tried some other stuff, too, but I think the things I've tried are basically different ways of doing the same thing.

Here is the code for the RepoTable component:

class RepoTable extends Component {
constructor(props) {
    super(props);
    this.state = {
        repos: this.get_repos()
    }
}


get_repos(){
    const repo_list = [];
    axios
        .get("https://api.github.com/users/Geno1131993/repos")
        .then(function(response){
            for(let i = 0; i < response.data.length; i++){
                repo_list.push(response.data[i]);
            }
        });
    return repo_list;
}


render() {
    const repo_list = this.state.repos.map( (repo) => {
       return (<Repo key = {repo.name} repo = {repo}></Repo>);
    });

    return (
        <ul key="repo_table">
            {repo_list}
        </ul>
    );
}

Here is the code for the Repo component (the random function assigns a random ID to the list item, and it gets assigned an icon in the CSS depending on what its name is determined to be):

class Repo extends Component {
constructor(props) {
    super(props);
    this.state = {
        repo: props.repo
    }
}

random(){
    let key = Math.floor((Math.random() * 5) + 1);
    switch(key){
        case 1:
            return "lotus1";
        case 2: 
            return "lotus2";
        case 3:
            return "lotus3";
        case 4:
            return "lotus4";
        case 5:
            return "lotus5";
    }
    return -1;
}



render() {
    return (
        <li key = {this.state.repo.name} className="repo">
            <a href={this.state.repo.html_url} rel="noreferrer" target="_blank">
                <div className="repo_title">{this.state.repo.name}</div>
                <div className="repo_info">
                    <p id={this.random()} className="lotus"></p>
                    <p className="repo_description">{this.state.repo.description}</p>
                </div>
            </a>
        </li>
    );
}

I don't receive any error messages, I just see an empty table -- thank you in advance for your thoughts and feedback!

Upvotes: 1

Views: 33

Answers (1)

Drew Reese
Drew Reese

Reputation: 202686

Issue

The constructor is synchronous so it can't and won't wait for the data fetch made in get_repos to resolve. Your initial repos state is incorrect. You also can't make asynchronous calls to set initial state from within the constructor, do this after the component mounts and actually save the response into state.

Solution

class RepoTable extends Component {
  constructor(props) {
    super(props);
    this.state = {
      repos: [] // <-- valid initial empty array
    };
  }

  componentDidMount() {
    this.get_repos(); // <-- fetch upon mounting
  }

  get_repos = async () => {
    axios
      .get("https://api.github.com/users/Geno1131993/repos")
      .then(response => {
        this.setState({
          repos: response.data // <-- update state with response array
        });
      });
  };

  render() {
    const repo_list = this.state.repos.map((repo) => {
      return <Repo key={repo.name} repo={repo}></Repo>;
    });

    return <ul key="repo_table">{repo_list}</ul>;
  }
}

Upvotes: 1

Related Questions