Noob question - Javascript resets when hande_events are triggered?

Sorry for the noob question if its obvious, but I’m kind of confused.

I was under the impression that liveview only refreshes part of the display relevant to what the code is changing, but if I try to add javascript to change something like the height or width of a div, the moment I submit a form or handle_event the javascript state refreshes. So if my starting state is 20px, the JS changes it to 40px and I submit a handle_event the state reverts back to 20px.

Do values need to be saved in the socket/database for them to survive? Or am I missing something with javascript?

1 Like

Sounds like you can either use phx-update=“ignore” the element or put the value in assigns and update it via hooks.

https://hexdocs.pm/phoenix_live_view/dom-patching.html

1 Like

It sounds like you’re crashing the liveview and forcing a reconnect, check to make sure your liveview code isn’t crashing.

1 Like

Yeah I found “ignore” earlier, but if you put ignore on the container of something like below thats in the guide, it will overwrite it. I have a handle_info that updates within the container, and ignore just disables everything.

def handle_info({:update_message, message}, socket) do
  {:noreply, update(socket, :messages, fn messages -> [message | messages] end)}
end

I found this guide which I think is meant to solve my issue

I’m having somewhat mixed success though as I have two different hooks and one seems to work and save to the sessionStorage and completely ignore handle_events whilst the other reloads whenever a handle_event is triggered, but its values are saved upon refresh.

I asked the question earlier because I wasn’t sure if I was just doing something silly in terms of not disabling livereloader properly, or to find out if I even need to disable livereloader

Nah its not crashing, I’m just unsure on how to save my JS changes to the socket I think.
I’m quite comfortable playing with databases, functions and using basic liveview, but haven’t really had to do anything that’s temporary and my JS is basically at a level where I’m comfortable reading it or rearranging it rather than fully understanding it off the top of my head.

The issue seems to be that my handle_events are triggered by links rather than live_patches, so when I click them I trigger a page reload.

I believe I have to use pushEvents but the hexdocs basically just mention them and don’t go into enough detail for someone like me to understand
https://hexdocs.pm/phoenix_live_view/js-interop.html

1 Like

You may still have to use the phoenixcustomhook npm library, or you can hijack on click to send a phx-click event to the server :expressionless: not sure if 0.18 gives a way to do it better

If you attach your phx-click attribute to a button element instead of an anchor do you experience the same behavior?

So use a button rather than a link? That might work but I think the bigger issue is figuring out how to do it both ways, as I’d rather not resort to dodging links entirely. I basically need to learn how send JS values to the socket which I haven’t done before.

I think from the looks of it I need to:

  • Set a default socket value

  • Use a phx-hook for the JS

  • Use pushEvent to update the default socket value for the current liveivew

  • Use sessionStorage to save the settings on reload

  • Add a condition so that the default socket value is the sessionStorage value if one exists

Or…I could just add it as a database field and be done in 10 minutes instead of another 2 hours, but unfortunately learning is necessary for growth :smiley: and that would be tremendously inpractical

1 Like

I asked that question, because it sounded like from your earlier message that you were using a link for something that wasn’t navigation related. Meaning you were performing an action, and buttons are for performing actions.

How set are you on using sessionStorage? A much easier option would be to keep this state in the URL.

You can get the current value or use default in handle_params:

def handle_params(params, _, socket) do
  size = Map.get(params, "size", "20")
  {:noreply, assign(socket, :size, size)}
end

Then, change the value server-side with push_patch:

def handle_event("change_size", %{"size" => size}, socket) do
  {:noreply, push_patch(socket, to: ~p"/path?size=#{size}")}
end

Or, client-side:

<.link patch={~p"/path?size=40"}>Increase to 40</.link>

Edit: I removed the session storage bit to focus on getting the pushevent to work as I hadn’t included one originally.

My current progress is that I’ve set an assign in the mount

     |> assign(:minimized, false)

Got a handle event for it

def handle_event("toggle_minimized", %{"minimized" => minimized}, socket) do
    {:noreply, assign(socket, minimized: minimized)}
end

Got a hook

const Hooks = {};
    
Hooks.Settings = {
  mounted() {
    const container = document.getElementById("container");
    const minimizeButton = document.getElementById("minimize");

    let minimized = this.el.dataset.minimized;

    console.log("Initial value of minimized:", minimized);

    minimizeButton.addEventListener("click", () => {
      minimized = !minimized;

      console.log("New value of minimized:", minimized);

      if (minimized) {
        container.classList.remove("active");
      } else {
        container.classList.add("active");
      }
      this.pushEvent("toggle_minimized", { minimized: minimized });
    });
  }
};

Its a work in progress though as I haven’t figured out how to extract the initial socket value. My “Initial value of minimized” in the console log comes back as undefined.

On the plus side though, I assume I’ve now learned how to use pushEvents as I can toggle between True and False in my second console log “New value of minimized”

Added this to the page to so I’m pretty confident I’ve at least learned something new as I can toggle the displayed text successfully the same as it displayed in the console.

<%= if @minimized === false do %>
  False
<% else %>
  <%= if @minimized === true do %>
    True
  <% end %>
<% end %>

So yeah its kind of going in the right direction…I think…unless someone tells me I’m going in the complete wrong direction. I asked the question earlier because I wasn’t even sure where to start on doing this, so feel like I’ve at least made some progress today.

This can be done much simpler without the use of hooks, but I’m not sure if you are using hooks as an exercise to learn them.

def mount(_, _, socket) do
  {:ok, assign(socket, :minimized, false)}
end

def handle_event("toggle_minimized", _, socket) do
  {:noreply, assign(socket, minimized: not socket.assigns.minimized)}
end
<button phx-click="toggle_minimized">Toggle</button>

Your example has changed from your original post. If you describe what it is you’re wanting to do, it’ll be easier to provide help.

I don’t think its changed, I just ended up exploring. I’ve had to remove the session saving to focus on getting the bits before to work. Currently I seem to be able to change the socket values using JS but have no idea how to extract or use socket values

My goal is still to keep changes made in JS when other events are triggered. I’m only using a hook because it looked like thats where all the guides were pointing. As I’m purely at a learning stage and have no experience in this I don’t really know whether I should just be using regular tags or hooks to be honest.

Just trying to follow whatever I can in the docs and figure things out basically.

I’m not entirely sure how the code you suggested will help me though? My issue isn’t getting the javascript to work, its getting it to hold its new state when events elsewhere on the page are triggered. Currently my pushevent seems to work fine as I can see the socket toggling between true and false, but my .active state which stores my different style values isn’t being added or removed.

I’ve currently entered the “I’m just going to save the values in a database” stage of this learning experience though and move on I think. In general I struggle with things for a few days then come back a month later and realise how I should have done it or what I did wrong from learning something else.

I would say hooks are more of a last resort. They are useful if you need to integrate with a 3rd party JavaScript library, or if you need to use some JavaScript API. Definitely not the first thing I’d reach for.

I was trying to show that you don’t need to use a hook for what you were doing in:

Here’s a complete example of that:

defmodule MyAppWeb.ToggleLive do
  use MyAppWeb, :live_view

  def handle_event("toggle_minimized", _, socket) do
    {:noreply, assign(socket, minimized: not socket.assigns.minimized)}
  end

  def mount(_, _, socket) do
    {:ok, assign(socket, :minimized, false)}
  end

  def render(assigns) do
    ~H"""
    <div class={if(@minimized, do: "active")} id="container">
      <button phx-click="toggle_minimized">Toggle</button>
      <%= @minimized %>
    </div>
    """
  end
end

Earlier, you were talking about using sessionStorage to store state on the client so that it would survive a reload. Which you correctly noted involved many moving parts:

I suggested using the URL to store that state instead, as you pretty much get the same results with much less work. Using the URL does mean someone can see the state changing in the querystring of the URL, but it does have the benefit of going back to a previous state by using the back button. Here’s a complete example of that:

defmodule MyAppWeb.ToggleLive do
  use MyAppWeb, :live_view

  def handle_params(params, _, socket) do
    minimized = Map.get(params, "minimized", "false") |> String.to_existing_atom()
    {:noreply, assign(socket, minimized: minimized)}
  end

  def render(assigns) do
    ~H"""
    <div class={if(@minimized, do: "active")} id="container">
      <.link patch={"/toggle?minimized=#{not @minimized}"}>Toggle</.link>
      <%= @minimized %>
    </div>
    """
  end
end

That’s a fine option too. I’m not sure what kind of values you’re dealing with. If it’s something that should be stored permanently, then that’s definitely the right place to do it.

I decided to cheat and just avoid JS entirely :slight_smile: .
I set the div class to the value in the socket and then just used a handle_event to toggle between classes. Values are stored as long as I stay within the same liveview and don’t reset when events are triggered…that I’m aware of yet :wink:

<div class={@style}
|> assign(:style, "style-a")
.style-a {
    height: calc(100% - 3.6rem);
    width: 25rem;
    transition: all 1s ease-in-out;
  }
  
.style-b {
    height: calc(100% - 3.6rem);
    width: 40rem;
    transition: all 1s ease-in-out;;
}
  
.style-c {
    height: calc(50% - 3.6rem);
    width: 40rem;
    transition: all 1s ease-in-out;;
}

.style-d {
    height: calc(50% - 3.6rem);
    width: 25rem;
    transition: all 1s ease-in-out;;
}

.style-e {
    height: 2rem;
    width: 25rem;
    transition: all 1s ease-in-out;;
}
def handle_event("style", %{"style" => new_style}, socket) do
    {:noreply, assign(socket, :style, new_style)}
end
<button phx-click="style" phx-value-style="style-a">Button A</button>
<button phx-click="style" phx-value-style="style-b">Button B</button>
<button phx-click="style" phx-value-style="style-c">Button C</button>
<button phx-click="style" phx-value-style="style-d">Button D</button>
<button phx-click="style" phx-value-style="style-e">Button E</button>
1 Like