Eiji

Eiji

Hi, here is an example script:

Application.put_env(:sample, Example.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 5001],
  server: true,
  live_view: [signing_salt: "aaaaaaaa"],
  secret_key_base: String.duplicate("a", 64)
)

Mix.install(
  jason: "~> 1.0",
  phoenix: "~> 1.7.0",
  phoenix_live_view: "~> 0.20.0",
  plug_cowboy: "~> 2.5"
)

defmodule Example.ErrorView do
  def render(template, _), do: Phoenix.Controller.status_message_from_template(template)
end

defmodule Example.HomeLive do
  use Phoenix.LiveView, layout: {__MODULE__, :live}

  alias Phoenix.LiveView.JS

  def mount(_params, _session, socket) do
    {:ok, assign(socket, :count, 0)}
  end

  defp phx_vsn, do: Application.spec(:phoenix, :vsn)
  defp lv_vsn, do: Application.spec(:phoenix_live_view, :vsn)

  def render("live.html", assigns) do
    ~H"""
    <script src={"https://cdn.jsdelivr.net/npm/phoenix@#{phx_vsn()}/priv/static/phoenix.min.js"}></script>
    <script src={"https://cdn.jsdelivr.net/npm/phoenix_live_view@#{lv_vsn()}/priv/static/phoenix_live_view.min.js"}></script>
    <script>
      let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket)
      liveSocket.connect()
    </script>
    <style>
    </style>
    <%= @inner_content %>
    """
  end

  def render(assigns) do
    ~H"""
    <button class="fadeIn" phx-click={
      JS.toggle_class("fadeIn fadeOut", time: 5000)
      |> JS.push("example")
    }>Example</button>
    """
  end

  def handle_event("example", _params, socket) do
    IO.inspect(Time.utc_now())
    {:noreply, socket}
  end
end

defmodule Example.Router do
  use Phoenix.Router
  import Phoenix.LiveView.Router

  pipeline :browser do
    plug(:accepts, ["html"])
  end

  scope "/", Example do
    pipe_through(:browser)

    live("/", HomeLive, :index)
  end
end

defmodule Example.Endpoint do
  use Phoenix.Endpoint, otp_app: :sample
  socket("/live", Phoenix.LiveView.Socket)
  plug(Example.Router)
end

{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
Process.sleep(:infinity)

The problem is that JS.push/1 is called immediately without giving a chance for animation. Using this example you can reproduce the issue (see inspected time in logs).

I have no idea if it’s a bug or intended behaviour. All I know is that at the start of every handle_event/3 function body For now instead of inspecting time I’m adding: Process.sleep/1 call which looks like a workaround, but after adding it animation works as expected. I wonder if other people experienced the same and if so what did you do with that. Is Process.sleep/1 call good here or maybe you can recommend some alternative (within JS + handle_event/3).

Showing Posts 1 to 4

chrismccord

chrismccord

Creator of Phoenix

This is as designed. You want the interaction to happen as soon as possible, and the UI feedback from the animation can mask the latency. What the :time does is locks the UI from patching over the animation for that duration, so you don’t end up stomping on your animations. You definitely don’t want to sleep in your handlers. The animation time should almost always be 200-300ms and not much more. If you are trying to lock the UI for long periods because you are doing some heavily async work, consider using <.async_result> and handling loading states, but it’s not clear what your real app goals are so hard to say.

Eiji

Eiji OP

As said it’s about animations that are skipped by such behaviour. 5s is for debugging only and in normal case I do not “estimate” how long a job would run. I wanted to return back to said 200-300ms after I find a good solution.

If I understand you correctly I should still avoid Process.sleep/1 anyway and write something like the code below, right?

  def handle_event(msg, params, socket) do
    # wait for animation to end and continue work in handle_info/2
    Process.send_after(self(), {msg, params}, @animation_duration)
    {:noreply, socket}
  end

  def handle_info({msg, params}, socket) do
    # …
  end

Ah, one more thing. The reason I have problem with animation is most probably because push_patch/2 call in my code which is not part of this example. Since it’s intended I have to (somehow) wait for animation end before doing any live navigation. That’s why I mentioned Process.sleep/1 as a workaround.

Lucassifoni

Lucassifoni

For that kind of feature I would make the button transitionable after the first render, and its visibility would depend on UI state.

Change the state to reflect the fact work has started (and this hides the button), and when you hear back from the background work, change the state to reflect that work is done.

If you see the UI as the result of a pure function over a state, a bit like Elm does it, it’s way easier than juggling with timings. Of course we also have JS to do small bits of this client-side for optimistic updates, but this shouldn’t cover “how long does work run for”.

Process.sleep will freeze this specific liveview process for the connected user for the duration of the sleep and is to be avoided.

Eiji

Eiji OP

No I’m not waiting for anything except animation (current 5s, in final version ~200ms). First of all I want to toggle a class, so JS part is required as I can’t do that within handle_event (for sure I can create a Hook for that and let live process wait for message from JavaScript, but this is only more complicated).

Yeah and that’s why I found this intended behaviour weird.

Yeah, I know - that was just a quick workaround to see if timing is a problem. Process.send_after/3 makes more sense for me as it allows to receive other messages.


I simply expected that I can instruct JS to do one thing and after time passed do another one. Mentioned push_patch/2 I use in real code is called too fast not letting the animation to even start and because of that behaviour I have to wait “by hand” this or other way.

What I’m doing is to navigate to other live route (handled by same module and process) and want that to happen after some element fade out (I want url change). As above I have posted just a minimal script to show issue with timing which I simply got wrong. I was surprised to see it’s expected as therefore we cannot do any animations before live navigation without using either Process.sleep/1, Process.send_after/3. or any other better or worse solution.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews