# Changelog for v1.1
## Quick update guide
When updating from LiveView 1.0, you can also use [igniter](https://hexdocs.pm/igniter) to perform the following changes for you:
```bash
mix igniter.upgrade phoenix_live_view
```
Note: before the final release, you need to run `mix igniter.apply_upgrades phoenix_live_view:1.0.0:1.1.0` after upgrading to the latest release candidate instead.
Here is a quick summary of the changes necessary to upgrade to LiveView v1.1:
1. In your `mix.exs`, update `phoenix_live_view` to latest and add `lazy_html` as a dependency:
```elixir
{:phoenix_live_view, "~> 1.1"},
{:lazy_html, ">= 0.0.0", only: :test},
```
Note you may remove `floki` as a dependency if you don't use it anywhere.
2. Still in your `mix.exs`, prepend `:phoenix_live_view` to your list of compilers inside `def project`, such as:
```elixir
compilers: [:phoenix_live_view] ++ Mix.compilers(),
```
3. (optional) In your `config/dev.exs`, find `debug_heex_annotations`, and also add `debug_attributes` for improved annotations:
```elixir
config :phoenix_live_view,
debug_heex_annotations: true,
debug_attributes: true,
enable_expensive_runtime_checks: true
```
4. (optional) To enable colocated hooks, you must update `esbuild` with `mix deps.update esbuild` and then update your `config/config.exs` accordingly. In particular, append `--alias:@=.` to the `args` list and pass a list of paths to the `"NODE_PATH"` env var, as shown below:
```elixir
your_app_name: [
args:
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]},
```
## Colocated hooks
LiveView v1.1 introduces colocated hooks to allow writing the hook's JavaScript code in the same file as your regular component code.
A colocated hook is defined by placing the special `:type` attribute on a `
"""
end
```
Important: LiveView now supports the `phx-hook` attribute to start with a dot (`.PhoneNumber` above) for namespacing. Any hook name starting with a dot is prefixed at compile time with the module name of the component. If you named your hooks with a leading dot in the past, you'll need to adjust this for your hooks to work properly on LiveView v1.1.
Colocated hooks are extracted to a `phoenix-colocated` folder inside your `_build/$MIX_ENV` directory (`Mix.Project.build_path()`). See the quick update section at the top of the changelog on how to adjust your `esbuild` configuration to handle this. With everything configured, you can import your colocated hooks inside of your `app.js` like this:
```diff
...
import {LiveSocket} from "phoenix_live_view"
+ import {hooks as colocatedHooks} from "phoenix-colocated/my_app"
import topbar from "../vendor/topbar"
...
const liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
+ hooks: {...colocatedHooks}
})
```
The `phoenix-colocated` folder has subfolders for each application that uses colocated hooks, therefore you'll need to adjust the `my_app` part of the import depending on the name of your project (defined in your `mix.exs`). You can read more about colocated hooks in the module documentation of `Phoenix.LiveView.ColocatedHook`. There's also a more generalized version for colocated JavaScript, see the documentation for `Phoenix.LiveView.ColocatedJS`.
We're planning to make the private `Phoenix.Component.MacroComponent` API that we use for those features public in a future release.
Note: Colocated hooks require Phoenix 1.8+.
> #### Compilation order {: .info}
>
> Colocated hooks are only written when the corresponding component is compiled.
> Therefore, whenever you need to access a colocated hook, you need to ensure
> `mix compile` runs first. This automatically happens in development.
>
> If you have a custom mix alias, instead of
>
> ```
> release: ["assets.deploy", "release"]
> ```
>
> do
>
> ```
> release: ["compile", "assets.deploy", "release"]
> ```
>
> to ensure that all colocated hooks are extracted before esbuild or any other bundler runs.
>
> If you have a `Dockerfile` based on `mix phx.gen.release --docker`, ensure that `mix compile` runs before `mix assets.deploy`.
## Change tracking in comprehensions
One pitfall when rendering collections in LiveView was that they were not change tracked. If you had code like this:
```heex
```
When changing `@items`, all elements were re-sent over the wire. LiveView still optimized the static and dynamic parts of the template, but if you had 100 items in your list and only changed a single one (also applies to append, prepend, etc.), LiveView still sent the dynamic parts of all items.
To improve this, LiveView prior to v1.1 had two solutions:
1. Use streams. Streams are not kept in memory on the server and if you `stream_insert` a single item, only that item is sent over the wire. But because the server does not keep any state for streams, this also means that if you update an item in a stream, all the dynamic parts of the updated item are sent again.
2. Use a LiveComponent for each entry. LiveComponents perform change tracking on their own assigns. So when you update a single item, LiveView only sends a list of component IDs and the changed parts for that item.
So LiveComponents allow for more granular diffs and also a more declarative approach than streams, but require more memory on the server. Thus, when memory usage is a concern, especially for very large collections, streams should be your first choice. Another downside of LiveComponents is that they require you to write a whole separate module just to get an optimized diff.
LiveView v1.1 changes how comprehensions are handled to enable change tracking by default. If you only change a single item in a list, only its changes are sent. To do this, LiveView uses an element's index to track changes. This means that if you prepend an entry in a list, all items after the new one will be sent again. To improve this even further, LiveView v1.1 introduces a new `:key` attribute that can be used with `:for`:
```heex
```
LiveView uses the key to efficiently calculate a diff that only contains the new indexes of moved items. Change tracking in comprehensions comes with a slightly increased memory footprint. If memory is a concern, you should think about using streams.
## Types for public interfaces
LiveView v1.1 adds official types to the JavaScript client. This allows IntelliSense to work in editors that support it and is a massive improvement to the user experience when writing JavaScript hooks. If you're not using TypeScript, you can also add the necessary JSDoc hints to your hook definitions, assuming your editor supports them.
Example when defining a hook object that is meant to be passed to the `LiveSocket` constructor:
```javascript
/**
* @type {import("phoenix_live_view").HooksOptions}
*/
let Hooks = {}
Hooks.PhoneNumber = {
mounted() {
this.el.addEventListener("input", e => {
let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
if(match) {
this.el.value = `${match[1]}-${match[2]}-${match[3]}`
}
})
}
}
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, ...})
...
```
Example when defining a hook on its own:
```javascript
/**
* @type {import("phoenix_live_view").Hook}
*/
Hooks.InfiniteScroll = {
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-more", {})
}
})
},
updated(){ this.pending = this.page() }
}
```
Also, hooks can now be defined as a subclass of `ViewHook`, if you prefer [native classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class):
```javascript
import { LiveSocket, ViewHook } from "phoenix_live_view"
class MyHook extends ViewHook {
mounted() {
...
}
}
let liveSocket = new LiveSocket(..., {
hooks: {
MyHook
}
})
```
Using [`@types/phoenix_live_view`](https://www.npmjs.com/package/@types/phoenix_live_view) (not maintained by the Phoenix team) is no longer necessary.
## `<.portal>` component
When designing reusable HTML components for UI elements like tooltips or dialogs, it is sometimes necessary to render a part of a component's template outside of the regular DOM hierarchy of that component, for example to prevent clipping due to CSS rules like `overflow: hidden` that are not controlled by the component itself. Modern browser APIs for rendering elements in [the top layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer) can help in many cases, but if you cannot use those for whatever reasons, LiveView previously did not have a solution to solve that problem. In LiveView v1.1, we introduce a new `Phoenix.Component.portal/1` component:
```heex
<%!-- in some nested LiveView or component --%>
<.portal id="my-element" target="body">
<%!-- any content here will be teleported into the body tag --%>
```
Any element can be teleported, even LiveComponents and nested LiveViews, and any `phx-*` events from inside a portal will still be handled by the correct LiveView. This is similar to [`` in Vue.js](https://vuejs.org/guide/built-ins/teleport) or [`createPortal` in React](https://react.dev/reference/react-dom/createPortal).
As a demo, we created [an example for implementing tooltips using `Phoenix.Component.portal`](https://gist.github.com/SteffenDE/f599405c7c2eddbb14723ed4f3b7213f) as a single-file Elixir script. When saved as `portal.exs`, you can execute it as `elixir portal.exs` and visit `http://localhost:5001` in your browser.
## `JS.ignore_attributes`
Sometimes it is useful to prevent some attributes from being patched by LiveView. One example where this frequently came up is when using a native `