> For the complete documentation index, see [llms.txt](https://aksel.gitbook.io/elm-workshop/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://aksel.gitbook.io/elm-workshop/task-3.md).

# Task 3

As we saw at the end of the last task, the server actually returned some comments as a response, when we created a new comment. What we didn't see is that the server also has an endpoint to fetch all the comments on an article.

In this task we will make a request to the endpoint when the page loads, and display the comments on an article to the user. We will also update the list of comments when the user posts a new comment.

## Task 3.1: Making a GET request

We start by preparing our model for the new state:

Similarly to the model for the entire page, we will create a custom type representing the state of the comments we are fetching. We could choose to do this in other ways, but we want to achieve the following things with our model:

* Represent that fetching the comments either is in progress, has failed or has succeeded.
* Allow for the page to display the article, regardless of whether fetching the comments failed.

Other priorities will result in another representation.

1. Create a new custom type in the page we have been working on (`src/Page/Article`), with the following definition:

   ```
    type CommentsState
        = LoadingComments
        | FailureGettingComments Http.Error
        | SucceededGettingComments (List Comment)
   ```

   You will need to import the `Comment` type, using the following line:

   ```
    import Comment exposing (Comment)
   ```
2. Next, add a field `comments` to `SuccessModel`, with the type `CommentsState`, and follow the compilation errors until your code compiles.
3. In this task we will save the view to the end, so we continue by making the request to the server. To do this, we start by making another message, for when fetching the comments is done. The message can be identical to `SavingCommentFinished`, except that you call it `FetchingCommentsFinished`. Add `FetchingCommentsFinished` to `Msg`, and make the required changes to the `update` function. You can start by returning `( model, Cmd.none )` from `update`.
4. Now that your code compiles with the new message, we can handle the message a bit more correctly in `update`. Instead of just returning directly `( model, Cmd.none )`, add a case statement on `model` to check that we are in the `Success` state of the page.
5. Then add another case statement inside the `Success` case to check whether the `Result` we got in the message was `Ok` or `Err`. Set the `comments` field in `SuccessModel` accordingly (you can just hard code an empty list in the `Ok` case for now).
6. Next, we will create a function in the `Api` module, similar to the `createCommentOnArticle` we added in Task 2.4. Name the function `getComments` , with the following type signature:

   ```
    getComments : (Result Http.Error () -> msg) -> ArticleId -> Cmd msg
   ```

   And use the `Http.get` function to make the request, and the URL to the comment endpoint is `/api/article/{articleId}/comments`, where `{articleId}` is the article ID of the article you just fetched.

   `Http.get` is similar `Http.post`, except that it doesn't have a `body`. You can read it's documentation [here](https://package.elm-lang.org/packages/elm/http/latest/Http#get), and you can use `Http.expectWhatever`, like we did in `createCommentOnArticle`.
7. We will now use the function we just created, and we are going to make the request for comments when the `update` function receives the message `FetchedArticle`. Replace `Cmd.none` with the function call to `getComments` in the case for `FetchedArticle`.

In the browser, you should now see that the app makes a request to the comments endpoint. You can see it in both the Elm debugger, and in the Network tab in the browser.

Note: This isn't the most efficient place to make the request, since it will make the page wait for the article response before fetching the comments, instead of doing the two request simultaneously. In an optional task later, you can try to change the app to handle making the requests at the same time. But for now, we will go ahead with this strategy.

## Task 3.2: Decoding the comment response

As you can see in the browser, the app now make a request to the comments endpoint, and the response will look something like this (if you are on the "Modelling in Elm" page):

```javascript
[
  {
    "id": "q6dkYsTwdX",
    "username": "User 1",
    "text": "Functional programming SUXXX!"
  },
  {
    "id": "3ABrq4hXB_",
    "username": "User 2",
    "text": "No, you suck!!"
  },
  {
    "id": "keJKsI_jHl",
    "username": "User 3",
    "text": "I like him!"
  },
  {
    "id": "5OOsLzU_mw",
    "username": "User 4",
    "text": "I agree"
  },
  {
    "id": "LQb3OY4w19",
    "username": "User 5",
    "text": "I like modifying global variables 😊"
  }
]
```

We see that the endpoint returns a list of comments, and that each comment has a three fields: `id`, `username`, and `text`, all of with are strings.

To get access to these comments, we are going to have to decode them. Simply put, decoding is Elm's way of guaranteeing that the JSON data we get from a server is actually in the shape that we expect.

To decode the list of comments, we are first going to have to make a decoder for the `Comment` type. `Comment` is already defined in the `Comment` module in the file `src/Comment.elm`, where you can take a look at it.

We can see that a `Comment` is a one-constructor union type, with three fields: `id`, `username`, and `text`, the first of which is of type `CommentId`, while the remaining two are of type `String`.

This lines up quite well with what we are seeing in the response from the server, except that the `id` field is a `CommentId`. But actually, we don't have to decode every field we receive in a JSON object, so we can start by removing the `id` field, to make our job a bit easier.

1. Remove the `id` field from the `CommentInfo` record.
2. To make a decoder for `Comment`, we are going to need a couple of packages, so import add the following two lines to the imports:

   ```
    import Json.Decode exposing (Decoder)
    import Json.Decode.Pipeline exposing (optional, required)
   ```

   The decoder we are going to make, is going to have the type signature `Decoder Comment`. A decoder is sort of like a recipe for how the JSON should look. It is not function. But before we make our `Comment` decoder, we are going to make a decoder for `CommentInfo`.
3. Paste in the following code at the bottom of the file:

   ```
    commentInfoDecoder : Decoder CommentInfo
    commentInfoDecoder =
        Json.Decode.succeed CommentInfo
            |> required "text" Json.Decode.string
            |> required "username" Json.Decode.string
   ```

   Note: This isn't very easy to understand the first time you see it, and you really don't need to understand what's going on here to continue with the tasks. So read the brief explanation below, and move on to the next task.

   The code above uses the fact that type aliases for records automatically get a constructor of the same name as the type alias (`CommentInfo` in this case), where the arguments to the constructor are in the same order as the fields in the type alias. So the constructor called `CommentInfo` is just a function with the type signature:

   ```
    CommentInfo: String -> String -> CommentInfo
   ```

   The `required` lines below then pick out the values from the fields in the JSON object called "text" and "username", respectively, and decode the values in the those fields as strings (that's the `Json.Decode.string` part). The two resulting strings are then passed to the `CommentInfo` constructor in the order they are in, and the result is a `Decoder CommentInfo`.

   This is a simplefied explanation, so if you're thinking "that's not entirely correct", then you are right. But it is sufficient for now.
4. We now have a `Decoder CommentInfo`, but what we ultimately need is a `Decoder Comment`. To get that we are going to use [`Json.Decode.map`](https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#map). `map` has the following type signature:

   ```
   map : (a -> value) -> Decoder a -> Decoder value
   ```

   In our case, we want to end up with a `Decoder Comment`, so `value` in the type signature is our `Comment`. `a` in this type signature is going to be our `CommentInfo`, since that is the decoder that we already have. If we write out the type signature again using our types, we get the following type signature:

   ```
   map : (CommentInfo -> Comment) -> Decoder CommentInfo -> Decoder Comment
   ```

   The second argument to `map` is going to be `commentInfoDecoder`, so what we need is the first argument, a function that takes a `CommentInfo` as an argument and returns a `Comment`. And actually, the one constructor in the `Comment` type is the function. It takes one argument, which is a `CommentInfo`, and returns a `Comment`.

   Given all that, we can add the following decoder for `Comment`:

   ```
   decoder : Decoder Comment
   decoder =
       Json.Decode.map Comment commentInfoDecoder
   ```
5. To use the decoder outside the `Comment` module, we are going to have to expose it. Add `decoder` to the list exposed by the `Comment` module.

   Next, we are going to actually use the decoder to get the comments that the comments endpoint returns.
6. In the `Api` module, in the `getComments` function, replace the call to `Http.expectWhatever` with the following line (your code won't compile anymore, but we will fix that in the next steps):

   ```
    Http.expectJson msg (Json.Decode.list Comment.decoder)
   ```

   This line is saying that we no longer don't care about what the server returns, we now expect the endpoint to return JSON. We also say that the JSON should be decoded with decoder defined as `Json.Decode.list Comment.decoder`. `Comment.decoder` is the decoder we just made, and `Json.Decode.list` is a way to make a decoder for a list of a certain type. The decoder we pass to `expectJson` is therefore of type `Decoder (List Comment)` which is what we actually expect the endpoint to return.
7. The compilation error we now get, says that the second argument to `expectJson` is not what the compiler expects. However, the second argument (the decoder) is exactly what we want to pass to `expectJson`. The thing we actually want to change is the type of the `msg` argument. Change the type of the first argument, in the type signature of `getComments` to the following (this will result in another compilation error):

   ```
    (Result Http.Error (List Comment) -> msg)
   ```

   We have now changed what type of message the `getComments` function accepts, since a successful call to the comments endpoint now results in a list of comments.
8. The compiler still complains, but this time the compilation error is in `src/Page/Article`, since we are now calling `getComments` with the wrong first argument. Fix this error by changing `FetchingCommentsFinished` in `Msg` to be of the type that `getComments` expects.

   After fixing this, your code should compile. And if you check the Elm debugger in the browser, you will see that you actually receive a message with a list of comments!
9. You can now change the hard coding we did earlier in the `FetchingCommentsFinished` case in `update`, so that we put the actual list of comments in the model, instead of always using the empty list.

If you check the Elm debugger in the browser now, you should see an actual list of comments in the model after the comments are finished loading.

## Task 3.3: A view for the comments

```
    div [ class "comment-section" ]
        [ h2 []
            [ text "3 comments"
            ]
        , div [ class "comments" ]
            [ div [ class "comment" ]
                [ div [ class "comment-username" ]
                    [ text "User 1" ]
                , div [ class "comment-text" ]
                    [ text "Text of the first comment" ]
                ]
            , div [ class "comment" ]
                [ div [ class "comment-username" ]
                    [ text "User 2" ]
                , div [ class "comment-text" ]
                    [ text "Text of the second comment" ]
                ]
            ]
        , viewWriteComment model
        ]
```

## Task 3.4: Updating the list of comments after a post

You can use the response from the POST request, to update the model.

## Task 3.5: Nested comments

The comments you get in the response when posting a comment are nested: each comment has a field `comments`, which is a new list of comments. Use the endpoint `/api/article/{articleId}/nestedComments`, in `Api.getComments` to get nested comments. Decode the comments with subcomments.
