Event handling and state updates with React
At the beginning of this project, it would be great to have a simple textarea where you can click a button and then have a new post added to the static posts array we wrote in the App class.
Add this above the div with the feed class:
<div className="postForm">
<form onSubmit={this.handleSubmit}>
<textarea value={postContent} onChange={this.handlePostContentChange}
placeholder="Write your custom post!"/>
<input type="submit" value="Submit" />
</form>
</div>
You can use forms in React without any problems. React can intercept the submit event of requests by giving the form an onSubmit property, which will be a function to handle the logic behind the form.
We are passing the postContent variable to the value property of textarea to have what's called a controlled component.
Create an empty string variable at the state property initializer, as follows:
state = {
posts: posts,
postContent: ''
}
Then, extract this from the class state inside the render method:
const { posts, postContent } = this.state;
Now, the new state variable stays empty, although, you can write inside textarea. This issue occurs because you are directly changing the DOM element but did not bind the change event to an existing React function. This function has the task of updating the React internal state that is not automatically connected to the browser's DOM state.
In the preceding code, we already passed the update function called this.handlePostContentChange to the onChange property of textarea.
The logical step is to implement this function:
handlePostContentChange = (event) => {
this.setState({postContent: event.target.value})
}
Maybe you are used to writing this a little differently, like this:
handlePostContentChange(event) {
this.setState({postContent: event.target.value})
}
Both variants differ a lot. Try it out for yourself.
When using the second variant, executing the function will lead to an error. The scope inside the function will be wrong, and you won't have access to the class via this.
In this case, you would need to write a constructor for your class and manually bind the scope to your function as follows:
this.handlePostContentChange = this.handlePostContentChange.bind(this);
You easily end up with five more additional lines of code when writing the constructor to bind the scope correctly.
The first variant uses the ES6 arrow function, which takes care of the right scope for you. I recommend this variant since it is very clean and you save time understanding and writing code.
Look at your browser again. The form is there, but it is not pretty, so add this CSS:
form {
padding-bottom: 20px;
}
form textarea {
width: calc(100% - 20px);
padding: 10px;
border-color: #bbb;
}
form [type=submit] {
border: none;
background-color: #6ca6fd;
color: #fff;
padding: 10px;
border-radius: 5px;
font-size: 14px;
float: right;
}
The last step is to implement the handleSubmit function for our form:
handleSubmit = (event) => {
event.preventDefault();
const newPost = {
id: this.state.posts.length + 1,
text: this.state.postContent,
user: {
avatar: '/uploads/avatar1.png',
username: 'Fake User'
}
};
this.setState((prevState) => ({
posts: [newPost, ...prevState.posts],
postContent: ''
}));
}
The preceding code looks more complicated than it is, but I am going to explain it quickly.
We need to run event.preventDefault to stop our browser from actually trying to submit the form and reload the page. Most people coming from jQuery or other JavaScript frameworks will know this.
Next, we save our new post in the newPost variable that we want to add to our feed.
We are faking some data here to simulate a real-world application. For our test case, the new post id is the number of posts in our state variable plus one. React wants us to give every child in the ReactDOM a unique id. By counting the number of posts, we simulate the behavior of a real back end giving us unique ids for our posts.
The text for our new post comes from the postContent variable from the component state.
Furthermore, we do not yet have a user system by now, that our GraphQL server can use to give us the newest posts, including the matching users with their avatars. We simulate this by having a static user object for all the new posts we create.
Finally, we update the component state again. This is where it gets a bit complicated. We are not passing an object as if we are doing it inside the handlePostContentChange function; we are passing an update function.
This approach gives us the current state reliably. Generally, I recommend using a function instead of using just an object. It automatically protects you against problems of race condition, where multiple functions manipulate the state. Always have in mind that the setState function is asynchronous.
The return value of the function is the state object we would normally have used directly. Thanks to the ES6 spread operator, we can prepend the newPost variable before the old posts, which will render the latest post at the top of our list. The textarea is cleared by passing an empty string into setState for the postContent field.
Now go ahead and play with your working React form. Do not forget that all posts you create do not persist since they are only held in the local memory of the browser and not saved to a database. Consequently, refreshing deletes your posts.