Building a Full Stack Application From Scratch with Svelte and Node (PART 3) — Viewing Stories and Comments

Dec 4 2019 | By Taha Shashtari

On the homepage we have a list of stories. Clicking on the title or the comments label doesn't open the story in a new page yet. So that's what we're going to do in this part.

Also, I'm going to show you how to display some mocked comments with their nested replies.

Let's begin!

Adding view story route

The URL of a single story would look like this: /story/[StoryId]. So let's create that route.

Two steps to create that route. First, we have to tell Sapper that the story id should be preceded by /story/ in the URL. And we can do that by creating a new folder inside src/routes named story.

Next, we need to tell Sapper that the second part of the URL is a dynamic parameter. We can do that by surrounding the parameter name with square brackets, like [id].svelte. So create [id].svelte inside src/routes/story/.

Now when you hit the link localhost:3000/story/123 (where 123 is the id), the src/routes/story/[id].svelte component will be displayed.

Since we're still dealing with mocked data, let's update the link of all stories to /story/123. So open src/_components/Story.svelte, and update the link of the mocked story like this:

let story = {
  score: 0,
  title: 'First Story',
  user: {
    username: 'user'
  },
  link: '/story/123',
  commentsCount: '2 comments',
  createdAt: '2 days ago'
}

To make sure it's working, try to click on the story title on the homepage. You should not see a 404 page. You should see a blank page instead.

Displaying the story

Open src/routes/story/[id].svelte and put the following:

<script>
  import Story from '../_components/Story.svelte'
</script>

<div class="item-page page">
  <Story/>
</div>

If you check the browser at localhost:3000/story/123, you should see the same mocked story displayed at the top.

Now let's display its comments below it.

Creating CommentContainer component with mocked data

Instead of displaying all the comments directly inside [id].svelte component, we will display them inside another component named CommentContainer. And that's because this component will also contain the comment input, the loading state, and the empty state of comments. (We won't add these in this part, we'll handle them later.)

So create _CommentContainer.svelte inside src/routes/story/ folder.

Now fill this component with this:

<script>
  import Comment from './_Comment.svelte'
  let comments = [
    {
      score: 0,
      _id: '1',
      content: 'first comment',
      user: {
        username: 'test user',
      },
      createdAt: '5 minutes ago',
      replies: []
    },
    {
      score: 0,
      _id: '2',
      content: 'second comment',
      user: {
        username: 'test user 2',
      },
      createdAt: '3 minutes ago',
      replies: [
        {
          score: 0,
          _id: '3',
          content: 'first reply',
          user: {
            username: 'test user',
          },
          createdAt: '2 minutes ago',
          replies: []
        }
      ]
    }
  ]
</script>

<div class="comment-list">
  {#each comments as comment (comment._id)}
    <Comment
      {comment}
    />
  {/each}
</div>

It's displaying a list of mocked comments. We don't have the <Comment> component yet. So let's create it.

But before that, let's display this component inside [id].svelte.

<script>
  import Story from '../_components/Story.svelte'
  import CommentContainer from './_CommentContainer.svelte'
</script>

<div class="item-page page">
  <Story/>
  <CommentContainer/>
</div>

Creating Comment component

In src/routes/story/, create ‌_Comment.svelte and put the following:

<script>
  export let comment
</script>

<div class="comment">
  <div class="details">

    <div class="voter">
      <span class="upvoter">
        <span class="arrow"></span>
      </span>
      <span class="score">{comment.score}</span>
    </div>

    <div class="main">
      <div class="byline">
        <span class="author">{comment.user.username}</span>
        <span class="date">{comment.createdAt}</span>
      </div>
      <p class="comment-content">{comment.content}</p>
    </div>
  </div>
</div>

So it's accepting the comment data as a prop and displaying its details as shown above.

If you check your browser, you should see this:

Displaying nested replies with

Each comment should have replies array to contain the replies to that comment. A reply is also a comment, which means it should also contains a list of replies. As you can see we're talking about unlimited nested comments.

If you take a look at the mocked comment data, you'll see that the second comment contains a reply inside its replies array but we're not displaying it.

To display it, we need to display the list of replies inside the Comment component. And since replies are also comments, we need to use the same component to display them.

You can't display the same component inside itself unless you use <svelte:self>.

Let's see that in code.

In _Comment.svelte, add this at the end of the file (before the last closing div):

{#if comment.replies && comment.replies.length}
  <div class="replies">
    {#each comment.replies as reply (reply._id)}
      <svelte:self
        comment="{reply}"
      />
    {/each}
  </div>
{/if}

So we're checking if replies is not empty. If it's not, we loop through them and display them using <svelte:self>.

In your browser, you should see the reply displayed like this:

What's next?

In the next part, we'll continue working on the comments section. We'll add the comment and reply input so users can add comments to stories and replies to comments. Stay tuned!