¡Ayúdanos a traducir! Vea nuestra guía de traducción: Guía de traducción
Our amazing blog posts will obviously garner a huge and passionate fanbase and we will very rarely have only a single comment. Let's work on displaying a list of comments.
Let's think about where our comments are being displayed. Probably not on the homepage, since that only shows a summary of each post. A user would need to go to the full page to show the comments for that blog post. But that page is only fetching the data for the single blog post itself, nothing else. We'll need to get the comments and since we'll be fetching and displaying them, that sounds like a job for a Cell.
Couldn't the query for the blog post page also fetch the comments?
Yes, it could! But the idea behind Cells is to make components even more composable by having them be responsible for their own data fetching and display. If we rely on a blog post to fetch the comments then the new Comments component we're about to create now requires something else to fetch the comments and pass them in. If we re-use the Comments component somewhere, now we're fetching comments in two different places.
But what about the Comment component we just made, why doesn't that fetch its own data?
There aren't any instances I (the author) could think of where we would ever want to display only a single comment in isolation—it would always be a list of all comments on a post. If displaying a single comment was common for your use case then it could definitely be converted to a CommentCell and have it responsible for pulling the data for that single comment itself. But keep in mind that if you have 50 comments on a blog post, that's now 50 GraphQL calls that need to go out, one for each comment. There's always a trade-off!
Then why make a standalone Comment component at all? Why not just do all the display in the CommentsCell?
We're trying to start in small chunks to make the tutorial more digestible for a new audience so we're starting simple and getting more complex as we go. But it also just feels nice to build up a UI from these smaller chunks that are easier to reason about and keep separate in your head.
But what about—
Look, we gotta end this sidebar and get back to building this thing. You can ask more questions later, promise!
Let's generate a CommentsCell:
Storybook updates with a new CommentsCell under the Cells folder, and it's actually showing something:
Where did that come from? Check out
CommentsCell.mock.js: there's no Prisma model for a Comment yet, so Redwood took a guess that your model would at least contain an
id field and just used that for the mock data.
Let's update the
Success component to use the
Comment component created earlier, and add all of the fields we'll need for the Comment to render to the
We're passing an additional
key prop to make React happy when iterating over an array with
If you check Storybook, you'll see that we do indeed render the
Comment component three times, but there's no data to display. Let's update the mock with some sample data:
Storybook refreshes and we've got comments! It's a little hard to distinguish between the two separate comments because they're right next to each other:
CommentsCell is the one responsible for drawing multiple comments, it makes sense that it should be "in charge" of how they're displayed, including the gap between them. Let's add a style to do that in
space-y-8is a handy Tailwind class that puts a space between elements, but not above or below the entire stack (which is what would happen if you gave each
<Comment>its own top/bottom margin).
Looking good! Let's add our CommentsCell to the actual blog post display page:
If we are not showing the summary, then we'll show the comments. Take a look at the Full and Summary stories in Storybook and you should see comments on one and not on the other.
Shouldn't the CommentsCell cause an actual GraphQL request? How does this work?
Redwood has added some functionality around Storybook so that if you're testing a component that itself isn't a Cell (like the
Articlecomponent) but that renders a cell (like
CommentsCell), then it will mock the GraphQL and use the
standardmock that goes along with that Cell. Pretty cool, huh?
Adding the comments to the article display has exposed another design issue: the comments are sitting right up underneath the article text:
Let's add a gap between the two:
Okay, comment display is looking good! However, you may have noticed that if you tried going to the actual site there's an error where the comments should be:
Why is that? Remember that we started with the
CommentsCell, but never actually created a Comment model in
schema.prisma or created an SDL and service! We'll be rectifying this soon. But this demonstrates another huge benefit of working with Storybook: you can build out UI functionality completely isolated from the api-side. In a team setting this is great because a web-side team can work on the UI while the api-side team can be building the backend end simultaneously and one doesn't have to wait for the other.
We added a component,
CommentsCell, and edited another,
Article, so what do we test, and where?
Comment component does most of the work so there's no need to test all of that functionality again in
Comment tests cover that just fine. What things does
CommentsCell do that make it unique?
- Has a loading message
- Has an error message
- Has a failure message
- When it renders successfully, it outputs as many comments as were returned by the
QUERY(what is rendered we'll leave to the
CommentsCell.test.js actually tests every state for us, albeit at an absolute minimum level—it make sure no errors are thrown:
And that's nothing to scoff at! As you've probably experienced, a React component usually either works 100% or blows up spectacularly. If it works, great! If it fails then the test fails too, which is exactly what we want to happen.
But in this case we can do a little more to make sure
CommentsCell is doing what we expect. Let's update the
Success test in
CommentsCell.test.js to check that exactly the number of comments we passed in as a prop are rendered. How do we know a comment was rendered? How about if we check that each
comment.body (the most important part of the comment) is present on the screen:
We're looping through each
comment from the mock, the same mock used by Storybook, so that even if we add more later, we're covered. You may find youself writing a test and saying "just test that there are 3 comments," which will work today, but months from now when you add more comments to the mock to try some different iterations in Storybook, that test will start failing. Avoid hardcoding data like this into your test when you can derive it from your mocked data!
The functionality we added to
Article says to show the comments for the post if we are not showing the summary. We've got a test for both the "full" and "summary" renders already. Generally you want your tests to be testing "one thing" so let's add two additional tests for our new functionality:
Notice we're importing the mock from a completely different component—nothing wrong with that!
We're introducing a new test function here,
waitFor(), which will wait for things like GraphQL queries to finish running before checking for what's been rendered. Since
CommentsCell we need to wait for the
Success component of
CommentsCell to be rendered.
The summary version of
Articledoes not render the
CommentsCell, but we should still wait. Why? If we did mistakenly start including
CommentsCell, but didn't wait for the render, we would get a falsely passing test—indeed the text isn't on the page but that's because it's still showing the
Loadingcomponent! If we had waited we would have seen the actual comment body get rendered, and the test would (correctly) fail.
Okay we're finally ready to let users create their comments.