Issues - Phoenix liveview update

Hi, I’m using liveview for displaying different sections on the same page and navigate through them with pill butons and live navigation. I have a filter bar to customize the data showed on each section by time and different parameteres.

Basically what I do is when the user selects all the filters it requires it fires this event when pussing the Search Button:

  def handle_event("apply_filters", _params, socket) do
    keys = [:companies, :countries, :states, :listings, :agents]

    Enum.map(keys, fn key ->
      update_filters(key, socket.assigns)
    end)

    send(self(), :load_data)

    {:noreply, assign(socket, loading: true)}
  end

Which basically update the state of the filters on the socket and put a loading template while the load_data event fetch the required data to be shown on the template:

  def handle_info(:load_data, %{assigns: assigns} = socket) do
    data = fetch_data(assigns.event, assigns.start_date, assigns.end_date, assigns.filters)
    {:noreply, assign(socket, loading: false, fetched_data: data)}
  end

I noticed that when I apply the filter changes all the “clickable items” stop working, like links and pagination arrow buttons. Also, I noticed this get fixed if I use the navigation to go to another section.
So when using the search button, the data loads correctly but all of the pagination buttons get freeze also links I’m using to display modals, and it gets fixed if I navigate to another section and return to the first one. I haven’t been able to find the cause of this behavior.

I’ll appreciate any help, thanks in advance!!

  • When does apply_filters fire?
  • Do you use any values from the socket for the disabled inputs? ie dates?
  • Does this method mutate anything? update_filters(key, socket.assigns) if it does, do you need the results back?

Hi buddy thanks for your reply, the apply_filters get fired when the Search button gets clicked.

  • yes sir, every “filter” of the navbar is represented with an state on the socket, this is done to have the correct filters data when applying them.
  • yes sir, it updates the filters components with the new data required for fetching data too on those components, ie. the listing filter, uses the filters data to fetch what it shows. I don’t need the results back since the goal of doing this is passing the filters data from the parent liveview to the child components (listing, agents, companies, date, etc) which are “Select” components which shows a dropdown with its own data,

I see. You are not assigning those filter values to the socket.

Depending on your mount, update you have a socket or html error when you partially come back to the page.

Hmm I don’t know if I understood you, I’m assigning those filter data on the mount:

def mount(%{"id" => event} = params, session, socket) when event in @events do
    embed = Map.has_key?(session, "embed")
    start_date = Date.utc_today() |> Date.add(-30)
    end_date = Date.utc_today()
    Gettext.put_locale(Map.get(params, "locale", "en"))
    company = Utils.format_company(params["company"])
    color = "#" <> Map.get(params, "color", "1b5081")

    filters =
      @allowed_filters
      |> Enum.map(&{&1, []})
      |> Enum.into(%{})
      |> Filters.apply_embed_params(params)

    state = [
      base_color: color,
      event: set_active_event(event),
      page_title: String.capitalize(event),
      start_date: start_date,
      end_date: end_date,
      loading: true,
      fetched_data: [],
      embed: embed,
      filters: filters,
      filter_labels: %{listings: [], agents: []},
      role: is_embed(embed, params),
      email: Map.get(params, "email", []),
      company: company,
      data: %{},
      loading_modal: true
    ]

    send(self(), :load_data)
    {:ok, assign(socket, state)}
  end

When applying filters and getting the issue with buttons, I change to the Leads section for example and go back to the views one and the problem is solved here is how I handle both section change and filters apply:

  def handle_event("change_event", %{"event" => event}, socket) when event in @events do
    redirect_to = Routes.live_path(socket, AnalyticsWeb.AnalyticsLive, event)

    {:noreply, push_patch(socket, to: redirect_to)}
  end

  def handle_event("change_event", _params, socket) do
    redirect_to = Routes.live_path(socket, AnalyticsWeb.AnalyticsLive, :views)
    {:noreply, push_redirect(socket, to: redirect_to)}
  end

  def handle_event("apply_filters", _params, socket) do
    keys = [:companies, :countries, :states, :listings, :agents]

    Enum.map(keys, fn key ->
      update_filters(key, socket.assigns)
    end)

    send(self(), :load_data)

    {:noreply, assign(socket, loading: true)}
  end

I thought this was an issue with my load_data clause, I checked the phoenix liveview documentation and I feel I’m doing everything well, ¿maybe I’m forgetting something or doing something wrong?

Sorry on the mobile. This is what I meant. You are sending socket.assigns and not putting anything on the socket.

So how does your LV know what to load and render on the load_data call

1 Like

Hmm I see, I not putting anything on the socket there because the filters state should already contain the updated values, since every time one of the child components changes it sends a message to the parent with the new values, for example the calendar sends the selected initial and end dates, so when load_data is called it uses the assigns dates.
Maybe this is because the approach I’m using for showing a loader while the load_data fetches the data? :grimacing:

so they all individually change the data and data is only loaded when you click the search button.

I think this is where you get out of sync. Your view shows a value and your socket has that value but your data is not filtered on that value.

You might get race conditions with this approach also. It an approach that is open to unintended actions.

It is very hard to debug the cycle from bits and pieces of code.

If your code is not open to push to some repository, best to enable livesocket.enabledebug() and follow each action with the correct action as well as actively monitoring your iex console.

I would put elaborate IO.inspect() on mount, update, and necessary updates, showing all the params and socket state.

Then you can pin the inconsistency hopefully.

2 Likes

Hmm I see, ok sir thank you very much for your answers, I will try to do that.
About my approach, what could be a better approach to handle this? I should handle all the logic from all the child components on the parent component? I did that basically to keep separated the behavior of each component and keep smaller the main liveview module.

You are already listing on search click anyway, I would have it on a form where possible and just do the same thing but explicitly assigning filter parameters to socket all at once.

1 Like

I got some help to find the solution for this issue if is useful for anyone. It was a synchronization issue, as @cenotaph told me. Calling the load_data like this solved the issue:
Process.send_after(self(), :load_data, 1000)

1 Like