# Adding Infinite scroll in Phoenix LiveView App

In my [previous](https://abulasar.com/fetching-data-from-graphql-api-in-phoenix-liveview-using-neuron) blog, I explained how to query Graphql API using Neuron. Throughout the blog, I explained the working of Neuron on page `0`. Whereas, there will be scenarios when users can have multiple blogs paginated on multiple pages. So, we need to add functionality so that users can load multiple pages. This can be achieved easily by adding a button with some text `Load More` and adding an event to `load` the data on the click. But we will be using a little different approach of using paginating using `Infinite Scroll`. This will be a continuation of my previous blog, so let's add this feature.

### What is Infinite Scroll?
Before we start, we first need to understand how Infinite scroll works. To get a fair idea about Infinite Scroll, refer to the gif image below.
![infinite-scroll.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1654528974404/P9sc1476O.gif align="left")

This is similar to what we see on Youtube, Facebook, Instagram, etc. It keeps user engage in the content and also keep away user from the hassle of clicking the `Next` button to load content.

### How Infinite Scroll works?
Infinite Scroll logic is very simple, we just have to add a javascript event listener which will listen to the `scroll` event. It will get triggered as soon as we reach the bottom (near the bottom) of the page. Below is the logic to detect the bottom of the page.
```
let scrollAt = () =>
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
  let clientHeight = document.documentElement.clientHeight
  return scrollTop / (scrollHeight - clientHeight) * 100
}
```
We just need to call this logic in the proper LiveView lifecycle and this can be achieved with LiveView JS Hooks.

### What is JS hook?
Phoenix LiveView often markets itself as that we can create reactive apps without writing JavaScript. But still, there comes a time when you have to write javascript. To integrate Javascript in our LiveView app, LiveView provides us with a concept of LiveView JS Hooks. JS Hooks allow us to register JavaScript lifecycle callbacks on DOM nodes.

### How does JS Hook work?
There are different types of lifecycle callbacks like `mounted`, `reconnected`, and `updated`. The `mounted` is the first callback that is called and initialized with some initial value as soon as the element on which we are applying it is mounted in the DOM. So, here we will add our `addEventListener` and initialize some value. We will create a new file for our hook file in `assets/js/infinite_list.js` and add the following code.
```
let scrollAt = () => {
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
  let clientHeight = document.documentElement.clientHeight

  return scrollTop / (scrollHeight - clientHeight) * 100
}

InfiniteList = {
  page() { return this.el.dataset.page },
  mounted(){
    this.pending = this.page()
    window.addEventListener("scroll", e => {
      if(this.pending == this.page() && scrollAt() > 90){
        this.pending = this.page() + 1
        this.pushEvent("load-blogs", {})
      }
    })
  },
  reconnected(){ this.pending = this.page() },
  updated(){ this.pending = this.page() }
}

export default InfiniteList
```
As discussed `mounted` callback is a place where we will add `eventListener` logic. It detects page bottom and triggers `pushEvent` of `load-blogs`. This will call `handle_event` callback in our `lib/fetch_hashnode_web/live/blogs_live.ex` which will have the following code logic.
```
def handle_event("load-blogs", _params, socket) do
    page_no = socket.assigns.page_no + 1
    new_blogs = page_no |> Blog.get_blogs
    blogs = socket.assigns.blogs ++ new_blogs
    socket = assign(socket, blogs: blogs, page_no: page_no)
    {:noreply, socket}
end
```
The logic here is self-explanatory we are fetching blogs using `Blog.get_blogs` by passing incremented `page_no` to it and assigning the `blogs` to the `socket` struct. 

### Adding `temporary_assigns`
One last change we have to make in the `blogs_live.ex` code. We have to use `temporary_assigns` for our `blogs` field in the `mount` callback. When we assign new blogs to `socket.assigns.blogs` there are chances that our data can grow, which will increase data in the socket, and ultimately our application performance will be affected. Refer to the following change
```
def mount(_params, _session, socket) do
    blogs = Blog.get_blogs()
    socket = assign(socket, blogs: blogs, page_no: 0)
    {:ok, socket, temporary_assigns: [blogs: []]}
end
```

### Adding the hook in LiveSocket
Now we will include our `InfiniteList` hook in our LiveSocket. For this, we will open the `app.js` file and import `InfiniteList` and include it in our Hooks object, and add the Hooks to our LiveSocket constructor.
```
import InfiniteList from "./infinite_list"

let Hooks = { InfiniteList }
....
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: Hooks }) 
// Notice `hooks: Hooks`
```
Now that we have added our `InfiniteList` hook to our hooks list. We will call this hook in our final arrangement.

### Calling the Hook 
To call our Javascript logic i.e hook we’ll add the `phx-hook` attribute on the `div` with value as the hook name i.e `InfiniteList` where we want to perform scrolling.
```
<div class="blogs" id="infinite-list" phx-hook="InfiniteList" phx-update="append" data-page={@page_no}>
   <%= for blog <- @blogs do %>
      // Display logic
   <% end %>
</div>
```
You will also notice the `data-page` attribute which has the value of `@page_no`. This attribute is the reason, that we were able to access the `page` number in the `page` method of the `InfiniteScroll` hook.
```
InfiniteList = {
  page() { return this.el.dataset.page },
  ...
```
The `phx-update=append` helps us to append or prepend the updates rather than replacing the existing contents. And don't miss to add the `id` attribute as well on the div. So, the following is the list of attributes that we need to apply on the div to enable javascript interactions.
- Hooks (`phx-hook="InfiniteList"`)
- phx-update (`phx-update=append`)
- data attribute (`data-page={@page_no}`)
- id (`id='infinite-list'`)

### Conclusion
For performing Infinite scroll we have to 
- Create hooks
- Add it to the hooks list in `LiveSocket` object
- Add `hook` on the div where you want to perform scrolling so that it can communicate with the javascript.

I hope you like this blog. If you have any questions then please comment below. Thanks for reading 😊.

### References
- [Cover Image](https://infinite-scroll.com/img/infinite-scroll-illo.png)
- [Infinite Scroll GIF](https://dribbble.com/shots/1876839-Infinite-Scroll)
- [JS Logic for detecting bottom screen](https://elixirforum.com/t/how-can-i-implement-an-infinite-scroll-in-liveview/30457/2)
