skip to Main Content

Heyia, React noob here, sorry for possibly dumb question, I just started few days ago and can’t really solve this.

I’m creating an array of objects containing data obtained from Twitter API and sending it back to App component where I want to work with it further.

I get the array, set it to the state of App component, but cannot achieve that the change is immediately shown on the page — I mean printing the list right after username is obtained from user input and being processed by the function returning the array.

It is probably something simple but I haven’t completely grasped the idea behind communication between components and their lifecycle (if those are relevant, I’m not really sure) so I cannot figure it out.

Can someone please help me?

This is my code

App.js file

class App extends Component {

constructor(props) {
    super(props);
    this.state = {
        username: "",
        tweets: []      // here I need to get the array of objects
    };

    this.submitHandler = this.submitHandler.bind(this);
}

// following methods are used to first get a username by user input

submitHandler = (event) => {
    event.preventDefault();
    this.useUsername();
};

usernameChangeHandler = (event) => {
    this.setState({username: event.target.value});
};

// this one's calling the function for obtaining the tweets, code below

useUsername = () => {
    this.setState({tweets: fetchTweets(this.state.username)});
};

render() {
    return (
        <div className="App">
            <Grid fluid>
                <Row>
                    <form>
                        <label>
                            Username:
                            <input type="text" value={this.state.username} onChange={this.usernameChangeHandler}/>
                        </label>
                        <button type="submit" onClick={this.submitHandler.bind(this)}>Submit</button>
                    </form>
                </Row>
                <Row>
                    <div className="jumbotron text-center">

                         // this should be updated when state changes
                        <Table list={this.state.tweets}/>
                    </div>
                </Row>
            </Grid>
        </div>
    );
}
} 

const Table = ({list}) => {
return (
    <div>
        {
            list.map((item) =>
                <div key={item.id}>
                    <h3>{item.text}</h3>
                    <h4>by {item.author}</h4>
                    <h5>by {item.date}</h5>
                    <p>{item.num_of_likes} Likes; Mentions: {item.mentions}; Hashtags: {item.hashtags}</p>
                </div>
            )
        }
    </div>
)
};

Function processing the user input and returning the array

export const fetchTweets = username => {
let alltweets = [];
twitter.get('statuses/user_timeline', {screen_name: username, count: 50}, function (error, tweets, response) {
    if (!error) {
        for (let i = 0; i < 50; i++) {
            let tw = tweets[i];
            let newtweet = new Tweet(i, tw.text, tw.user.screen_name, tw.created_at,
                tw.favorite_count, tw.entities.user_mentions, tw.entities.hashtags);
            alltweets.push(newtweet);
        }
    } else {
        console.log("Error occurred while retrieving tweets: " + error);
        return error;
    }
});
        return alltweets;

};

And this is the class of objects I’m creating from the Twitter data

export class Tweet {
constructor(id, text, author, date, num_of_likes, mentions, hashtags) {
    this.id = id;
    this.text = text;
    this.author = author;
    this.date = date;
    this.num_of_likes = num_of_likes;
    this.mentions = mentions;
    this.hashtags = hashtags;
}
}

Any help highly appreciated.

2

Answers


  1. The reason why the fetchTweets function doesn’t work right now is because the twitter.get function is asynchronous, meaning the stuff in the callback doesn’t happen immediately after you call it. It happens a little while later. Here’s what happens when you call it:

    • The alltweets array is created, set to []
    • twitter.get is called
    • The alltweets array is returned to the react component
    • React sets the state to an empty array
    • twitter.get finishes, has the results, and adds stuff to the array

    The missing piece here is that setState needs to happen after twitter.get is finished, not before. The most straight-forward way of accomplishing this is to allow your fetchTweets function to accept another function which gets called when twitter.get is finished. Here’s how to do that:

    // new fetchTweets
    export const fetchTweets = (username, handleResult) => {
      twitter.get("statuses/user_timeline", { screen_name: username, count: 50 }, function(error, tweets, response) {
        if (!error) {
          let alltweets = []
    
          for (let i = 0; i < 50; i++) {
            let tw = tweets[i]
            let newtweet = new Tweet(i, tw.text, tw.user.screen_name, tw.created_at, tw.favorite_count, tw.entities.user_mentions, tw.entities.hashtags)
            alltweets.push(newtweet)
          }
    
          // callback function is called here
          handleResult(alltweets)
        } else {
          console.log("Error occurred while retrieving tweets: " + error)
        }
      })
    }
    
    // in your react component
    useUsername = () => {
      fetchTweets(this.state.username, alltweets => {
        this.setState({ tweets: alltweets })
      })
    }
    
    Login or Signup to reply.
  2. Approach is not good one.

    Make your function fetchTweets to return promise resolved with data or rejected with error.

    export const fetchTweets = username => {
      let alltweets = [];
      return new Promise((resolve, reject) => {
        twitter
          .get('statuses/user_timeline', {
            screen_name: username,
            count: 50
          }, function (error, tweets, response) {
            if (!error) {
              ///construct alltweets
    
              resolve(alltweets);
            } else {
              reject(error);
            }
          });
      });
    };
    

    Instead of calling this.useUsername() from submitHandler,call fetchTweets instead and setState within it.

    submitHandler = (event) => {
      event.preventDefault();
      fetchTweets().then((alltweets) => {
        this.setState({tweets: alltweets})
      }).catch((error) => console.log(error));
    };
    

    Or if you can also try async / await instead of promise chain.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search