After fetching JSON data from an endpoint, how do I work with the response?

Viewed 26

I'm attempting to implement basic notifications in my App. I have setup a /notifications.json endpoint that serves as an index that returns all Notification for the current_user.

controller:

class NotificationsController < ApplicationController
  def index
    @notifications = Notification.where(recipient: current_user).unread
  end
end

notifications.json view

json.array! @notifications do |notification|
  json.actor notification.actor
  json.action notification.action
  json.notifiable notification.notifiable
  json.url thread_path(notification.notifiable)
end

/notifications.json

[
  {
    "actor": {
      "id": 1,
      "email": "test@user.com",
      "created_at": "2022-07-30T14:58:12.617Z",
      "updated_at": "2022-09-17T16:30:04.497Z",
      "name": "John Doe",
    },
    "action": "posted",
    "notifiable": {
      "id": 76,
      ...
    },
    "url": "/threads/76"
  }
]

Now to the JS part. I want to implement some sort of JS behaviour so when a page is loaded, the notifications are fetched and some HTML is replaced to show each notification in a dropdown (each notification being a hash from the array of hashes returned by the notifications.json endpoint).

I use StimulusJS for this part, so the controller is automatically connected when the HTML is loaded.

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "links", "template" ]

  connect() {
    this.fetch_notifications()
  }

  fetch_notifications() {
    fetch("/notifications.json")
      .then(
        (response) => {
          console.log(response)
        }
      )
  }
}

But this is as far as I can get. I've tried several things when working with the fetch() response but I haven't managed to get to the array of hashes that manually visiting /notifications.json returns.

console.log(response)

Response {type: "basic", url: "http://localhost:3000/notifications.json", redirected: false, status: 200, ok: true, …}
body: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "basic"
url: "http://localhost:3000/notifications.json"
__proto__: Response

console.log(response.body)

ReadableStream {locked: false}
locked: false
__proto__: ReadableStream
cancel: ƒ cancel()
getReader: ƒ getReader()
locked: false
pipeThrough: ƒ pipeThrough()
pipeTo: ƒ pipeTo()
tee: ƒ tee()
constructor: ƒ ReadableStream()
Symbol(Symbol.toStringTag): "ReadableStream"
get locked: ƒ locked()
__proto__: Object

console.log(response.json)

ƒ json() { [native code] }

I was expecting to get the same array of hashes in the response.body, but I do not.

Am I doing something wrong? Do I have to further process the fetch() response?

1 Answers

Notifications should be pushed by the server, not pulled by the client.Hotwire is made for this. You don't need js or json. On the initial page load, display the notifications (optional). (shown below using haml not html)

= turbo_stream_from current_user
#notifications
  = render @notifications

This assumes you have a partial called _notification.haml, look at the details of rendering collections here. The div with id #notifications is the container that will be used as a target for appending new notifications when they are created on the server.

The turbo_stream_from current_user statement establishes an actioncable channel identified by the current user.

When notifications are created, a callback broadcasts the notification to the user's browser, and you will set it up to append the new notification (using the _notification.haml partial) to the #notifications element.

#app/models/notification.rb
belongs_to :user

after_create do
  broadcast_append_to user, target: 'notifications'
end

This sends the notification as haml (or html) over the actioncable channel identified by the user.

Related