surface-ui / surface

A server-side rendering component library for Phoenix
https://surface-ui.org
MIT License
2.08k stars 150 forks source link

Socket assigns not always up-to-date with Ecto repo when using Surface UI syntax. #623

Closed coenbakker closed 2 years ago

coenbakker commented 2 years ago

I think that I might have ran into a bug in Surface UI when migrating a project from LiveView to LiveView + Surface UI.

In the original, LiveView-only project, whenever the data inside the repo changes and I refresh the page, the socket assigns change accordingly. That's what needs to happen. After migrating to LiveView + Surface UI the socket assigns do not always correctly reflect the data inside the Ecto repo. See examples below for more clarity.

This does NOT work.

defmodule AppWeb.MuffinsLive do
  use Surface.LiveView

  alias App.Muffins # context associated with muffins database

  muffins = Muffins.list_muffins() # gets data from Ecto repo
  data muffins, :list, default: muffins

  def render(assigns) do
    ~F"""
    <div>
      {inspect @muffins} # This does not always reflect the latest state of the database after a page refresh
    </div>
    """
  end
end

This does work.

defmodule AppWeb.MuffinsLive do
  use Surface.LiveView

  alias App.Muffins # context associated with muffins database

  def mount(_, _, socket) do
    muffins = Muffins.list_muffins() # gets data from Ecto repo
    {:ok, assign(socket, %{muffins: muffins})}
  end

  def render(assigns) do
    ~F"""
    <div>
      {inspect @muffins} # This always reflects the latest state of the database after a page refresh
    </div>
    """
  end
end

Environment

Elixir 1.13.3

defp deps do [ {:phoenix, "~> 1.6.6"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.6"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 3.0"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_view, "~> 0.17.5"}, {:floki, ">= 0.30.0", only: :test}, {:phoenix_live_dashboard, "~> 0.6"}, {:esbuild, "~> 0.3", runtime: Mix.env() == :dev}, {:swoosh, "~> 1.3"}, {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, {:gettext, "~> 0.18"}, {:jason, "~> 1.2"}, {:plug_cowboy, "~> 2.5"}, {:surface, "~> 0.7.4"} ] end

msaraiva commented 2 years ago

Hi @coenbakker!

The first example doesn't work because the Muffins.list_muffins() call is defined in the module scope, so it will be evaluated only once, at compile-time. The second example, which loads the list inside mount/3 is the right way to go. The default option can only be set to values that can be evaluated at compile-time, like literals, module attributes or functions returning values you don't expect to change.

coenbakker commented 2 years ago

I see. I assumed that both the data name, :type, default: value bit and the function call would, under the hood, be processed inside a mount function. Thank you for clarifying.

coenbakker commented 2 years ago

Initially (and naively) thought data muffins, :list, default: Muffins.list_muffins() might work.