Create a Comment Box and Comment Posting Function Using React JS
After finishing the Instagram clone project using vanilla JS, and now I am on track learning the basics of React and changing the vanilla JS codes that I wrote into React codes. The most important concepts of React are States and Props.
It took a lot of hassles just to understand what they mean and do. I am not even really comfortable with vanilla JS yet, but now that I have to learn React, it was just so much going on in my head.
Anyways, the main topic about this post is how to create a comment box with comment posting function using React. This post only refers to the UI, not considering the back-end. I am going to cover:
- How to set states for comment values input by the user
- How to enable ‘Post’ button on the comment box
- How to work with states and props, and post comments to the comment list
- Why applying a unique key to each comment is important
How to set states for comment values entered by the user & enable ‘Post’ button in the comment box
So, on the Instagram feeds, when nothing is entered in the comment input box, the ‘Post’ button is disabled (unable to click) and only when something is entered, the button gets enabled and turns blue. Please refer to the images below.
Let’s look at the codes below:
...
class Main extends Component {constructor() {
super(); this.state = {
commentValue: “”,
commentLine: [{ commentId:””, text: “”, }],
};
}...
- We need to start by setting the initial states of the component in the states object. We are on the Main component now, but eventually we are going to pass the states to the child component, CommentBox, through props. I am using
class
here, but you can also use function. - I set the initial
commentValue
(the values entered in the input element)’s state empty and it will be changed when something gets entered. We will talk about thecommentLine
state later in the post.
handleCommentValue = (e) => {this.setState({
commentValue: e.target.value,
});
};
3. Now, we need to change the state of commentValue
to what the user enters in the input element. This case is where we need an event handler function. I named it ashandleCommentValue
and when the e
(event) happens, setState()
function gets executed. setState()
changes the state to the assigned value which is e.target.value
— the value that gets entered by the user.
render() {
return (
<>
... <CommentBox
commentValue={this.state.commentValue}
handleCommentValue={this.handleCommentValue}
enterCommentLine={this.enterCommentLine}
submitCommentLine={this.submitCommentLine}
/>...
</>
4. Then, we need to pass the states and event handler function down to the child component where has the comment box elements. As the CommentBox
is a component, it can be returned in the parent component by writing <CommentBox />
. Inside the tag, the state and event handler function can be passed down by writing them like attributes — such as commentValue={this.state.commentValue}
. The names of attributes can be what you’d like, but I set them the same with the states’ name. We will get back to the last two states (enterCommentLine and submitCommentLine) later.
class CommentBox extends Component {render() { const { commentValue, handleCommentValue,
enterCommentLine, submitCommentLine} = this.props; const enableCommentButton = () => {
return (commentValue ? false : true);
} const changeCommentButtonStyle = () => {
return (commentValue ? "comments-button-enabled" :
"comments-button-disabled");
}return ( <div className="comments-box">
<input onKeyPress={enterCommentLine} value={commentValue}
id="comments-input" onChange={handleCommentValue}
type="text" placeholder="Add a comment..." /> <button onClick={submitCommentLine} type="submit"
className="comments-button"id={changeCommentButtonStyle()}
disabled={enableCommentButton()}>Post</button>
</div> )}
}
5. Let’s focus on the bolded codes for now. From #4, we passed down the commentValue
and handleCommentValue
to CommentBox component. To use them in the component’s elements, we need to set them as props
by writing const { commentValue, handleCommentValue} = this.props;
.
6. Note that the handleCommentValue
is assigned to onChange
attribute, which tracks the values entered by the user in the element and set the commentValue
to the values. Also, commentValue
is assigned to value
attribute to reset the input element when we submit/post the comment.
7. Inside render()
but outside of return()
, two functions are declared — enableCommentButton()
to get rid of the disabled
attribute from the Post button element, and changeCommentsButtonStyle()
to change the style of the button when it gets enabled. Then, the two functions are inserted to the button tag — id={changeCommentButtonStyle()} disabled={enableCommentButton()}
. Note the functions are wrapped with {} where you can write JS syntax in JSX. By applying the function to id
, we can work with CSS to apply the appropriate style.
How to work with states and props, and post a comment to the comment list
Now, we need to post the comment by pressing Enter or clicking the Post button. I created a Comment component which represents each comment (commentLine
) submitted by the user. But first, let’s continue working with the parent Main component and then CommentBox component. Now, the parent Main component has the state of commentLine.
this.state = {
commentValue: “”,
commentLine: [{ commentId:””, text: “”, }],
};
- Note that the
commentLine
is an array which contains each element of an object that hascommentId
andtext
data. We are going to talk about thecommentId
in the next section.
setCommentLine = () => {this.setState({
commentLine: [
…this.state.commentLine,
{ commentId: commentCounter++, text: this.state.commentValue }], commentValue: “”,
});
2. Continued in the parent Main component, I declared the setCommentLine
function that sets the commentLine
state. When we think about this state, it needs to change to whatever entered in the input element. So, thecommentValue
state is assigned to text
. After the commentLine
gets posted, it should be empty — commentLine: ""
. Now let’s make two more functions that execute setCommentLine
.
submitCommentLine = (e) => {
e.preventDefault();
this.setCommentLine();
};enterCommentLine = (e) => {
if (e.charCode === 13) {
this.setCommentLine();
}
};
3. setCommentLine
function needs to be executed when we post the comment by Enter or clicking the Post button. So, we need two even handler functions, one for clicking the button and the other one for pressing Enter. Inside each function, setCommentLine
function is executed. These two functions are then again passed down to the child CommentBox component.
...
return ( <div className="comments-box">
<input onKeyPress={enterCommentLine} value={commentValue}
id="comments-input" onChange={handleCommentValue}
type="text" placeholder="Add a comment..." /> <button onClick={submitCommentLine} type="submit"
className="comments-button"id={changeCommentButtonStyle()}
disabled={enableCommentButton()}>Post</button>
</div>)}
}
4. In the child CommentBox component, note that enterCommentLine
is assigned to onKeyPress
attribute of the input element, and submitCommentLine
is assigned to onClick
attribute of the button element.
Why applying a unique key to each comment is important
Now, let’s look at each comment element that gets added as the user posts the comment.
let commentCounter = 1;class Main extends Component {constructor() {
super();this.state = {
commentValue: “”,
commentLine: [{ commentId:””, text: “”, }],
};
}....setCommentLine = () => {this.setState({
commentLine: [
…this.state.commentLine,
{ commentId: commentCounter++, text: this.state.commentValue }], commentValue: “”,
});
- Before we move onto the child Comment component, let’s recall that
commentLine
has an object element withcommentId
andtext
properties.commentId
is a unique key given to each comment. Also, note that thecommentCounter
variable is declared in a global scope, which is used insetState()
to assigncommentId
a unique number.
class Comment extends Component {render() {
const { commentLine } = this.props; return (
<ul className=”comments-list”>
{commentLine.map((val) => {return
<li className=”each-comment”
key={val.commentId}>{val.text}
</li>
})
}
</ul>
)};
}
2. Now we are on the child Comment component. It needs to return the li
element with the data using props from the parent component. Since the commentLine
is an array with object elements that have two properties, we can use map()
method to work with and return each property. Here, commendId
is assigned to key
of the li
element, and text
to the value.
So why is it important to give a unique key to the elements in an array?
We use key
to give the elements in an array a stable identity. The stable identity helps to track which item is changed, added, or deleted. When React is recursing on the children of a DOM node, it iterates both lists of children at the same time and only mutates whenever there’s a difference. This means, if a new and different li
element goes into the beginning of other li
elements that have the same values from each other, it can cause a problem because React will mutate every child as if the elements are all different. More detailed explanation is here:
So, we need to give a unique key to the elements in an array to avoid inefficiency and possible issues with your application. I used the commentCounter
which increases by 1 for each element, but this cannot be an ideal way when the component is to be used over and over in your app. Ideally, the unique ids for key are to be given from the back-end, or you can also utilize libraries that generate random numbers.