Now that we’ve got our basic single component (named [slug].js), which displays a single post, let’s look at how easy it is to add the comments area.
First, let’s build 2 components: 1) CommentList.js and 2) CommentForm.js. In Next.js, when creating components that doesn’t use a route – we place them in the “components” directory.
Let’s go and add these components into our main [slug].js by importing them.
import Comments from '../../components/CommentList'
import CommentForm from '../../components/CommentForm'
Comment List
Before we continue, let’s make sure we have the right data to work with. That means we have to make a separate call to the WordPress API, fetching the list of comments, and add it to our props.
This part is still in [slug.js], inside the getServerSideProps() method.
So before our final return statement, let’s do an Axios call. Note that at this point, we would’ve exited – if no post was found.
const comments = await Axios.get(url + `comments?post=${ post.id }`);
The above “${ post.id }” should have a value – making it safe to do another call.
Let’s modify our output, right before we return it. to look like below:
output.props.comments = comments.data.length > 0 ? comments.data : null;
Now we have the comments in our props, we’re ready to pass it to our HTML. Open up the empty CommentList.js
export default ({ ...props}) => {
let numComments = props.comments !== null ? props.comments.length : 0;
return (
<div className="comments col-6 col-12-small">
<h2>{numComments} Comment(s)</h2>
{props.comments === null ? (
<p>Be the first one to comment...</p>
) : (
props.comments.map( comment => {
return (
<div key={comment.id} className="comment">
<img className="comment-avatar" src={comment.author_avatar_urls[48]} />
<h4 className="comment-author-name">{comment.author_name}</h4>
<div className="comment-content"
key={comment.id}
dangerouslySetInnerHTML={ { __html : comment.content.rendered } }>
</div>
</div>
)
})
)}
</div>
)
}
The code above is pretty self-explanatory. It shows a heading – with the number of comments. It then shows a message that there are no comments – if none is found.
If there are comments, we do a “map()” returning the piece of HTML code with the avatar, author name and the comment content.
Comment Form
Okay this part confuses me a bit. See all this time I’ve been working with server side React through Next.js. But implementing the code for the comment form involves almost all client side React.
Somehow, Next.js is able to switch between server / client – in one code base. I might be overthinking it – but as long as it works 😎
Open up the empty CommentForm.js and let’s with our imports.
import { Fragment, useCallback } from 'react'
import Axios from 'axios'
Now the default export – which is the HTML, including the form.
export default ({...props}) => {
return (
<Fragment>
<h2>Add a Comment</h2>
<form onSubmit={handleSubmit}>
<input onChange={fieldChangeHandler} type="text" name="commenter-name"/>
<input onChange={fieldChangeHandler} type="email" name="commenter-email"/>
<textarea onChange={fieldChangeHandler} name="commenter-message"></textarea>
<button type="submit" className="primary">Submit</button>
</form>
</Fragment>
)
}
Obviously this is a pretty basic form. But enough to demonstrate what we’re trying to do. Notice we have two functions that we have to build 1) fieldChangeHandler() – which happens when the input fields have been changed, and 2) handleSubmit() – when the form is submitted.
Still inside the export default method, add the code below.
let fields = {
author_name : '',
author_email : '',
content : '',
post : props.post_id //getting this from the main component
}
const fieldChangeHandler = (e)=>{
switch(e.target.name){
case 'commenter-name':
fields.author_name = e.target.value;
break;
case 'commenter-email':
fields.author_email = e.target.value;
break;
case 'commenter-message':
fields.content = e.target.value;
break;
}
}
The fields object is simply a container that holds the value of the input fields.
Notice the “post” property, with the value of props.post_id – don’t worry about that one. We’re getting this from our parent component later – so just leave it as is for now.
The fieldChangeHandler() function gets called every time we change the inputs. As mentioned previously, it simply holds the updated value of the fields. Let’s move on.
const handleSubmit = useCallback( async (e) => {
e.preventDefault();
const url = 'http://yourwordpress.url/wp-json/wp/v2/';
let response = await Axios({
method : 'post',
url : url + `comments`,
data : fields
}
);
console.log(response);
}, [])
Okay, I’m not going to lie, but the useCallback hook is a little confusing to me. But we’re using this hook for our handleSubmit method. I’ve also made the function being passed – an async method, so we can deal with the response accordingly.
First we prevent the default behavior – this is so that our browser doesn’t do a hard refresh when the form is submitted. then we do an await Axios post – passing in the fields object that we’ve setup. This will post to the WordPress API, with the form information that we’ve submitted.
Finally, let’s add it to our main HTML:
<CommentsList comments={props.comments} />
<CommentForm post_id={props.post.id}/>
Remember the post_id props that we’re using in the CommentForm component – we’re passing it in the code above.
One last thing to do in the WordPress side of things is to enable anonymous comments in the REST api. This can be done in the theme’s functions.php file:
add_filter( 'rest_allow_anonymous_comments', '__return_true' );
And that’s about all she wrote on comments.
Obviously we can add plenty of enhancements to this code such as success/error messaging, maybe show a preview of the submitted comment – and even adding captcha.