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
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:alltweets
array is created, set to[]
twitter.get
is calledalltweets
array is returned to the react componenttwitter.get
finishes, has the results, and adds stuff to the arrayThe missing piece here is that
setState
needs to happen aftertwitter.get
is finished, not before. The most straight-forward way of accomplishing this is to allow yourfetchTweets
function to accept another function which gets called whentwitter.get
is finished. Here’s how to do that:Approach is not good one.
Make your function
fetchTweets
to return promise resolved with data or rejected with error.Instead of calling
this.useUsername()
fromsubmitHandler
,callfetchTweets
instead andsetState
within it.Or if you can also try
async
/await
instead of promise chain.