DEV Community

Cover image for Renderizar componentes condicionalmente con Phoenix LiveView
Santiago Cardona
Santiago Cardona

Posted on • Edited on

Renderizar componentes condicionalmente con Phoenix LiveView

Cuando nos encontramos con una vista que tiene muchos elementos HTML, normalmente queremos separarlos en sus propios componentes para tener una mejor organización del código que facilite su comprensión y mantenimiento, así como desacoplar la lógica grande en piezas más pequeñas, y por qué no, reutilizar estos componentes en otra vista (muy al estilo del modelo mental de React.js). Esto se puede conseguir fácilmente utilizando LiveComponent de Phoenix LiveView.

El problema, es que muchas veces estos componentes comparten cierta lógica entre sí, y muchas veces dependen el uno del otro para saber si debe ser renderizado o no. A esto se le llama renderizado condicional, y con Phoenix LiveView podemos lograrlo manejando la lógica condicional en la LiveView padre, por medio de la función callback handle_info.

Caso de Uso

Supongamos que tenemos una vista para registrar un vehículo en un taller de mecánica. Esta vista se compone de dos formularios, uno para el registro del dueño del vehículo y otro para el registro del vehículo como tal. Cada formulario tiene su propia vista de registro independiente, y por ello usamos un LiveComponent por cada una de estos, dentro del cual se maneja su propia lógica de negocios independientemente.

live componets

Estas vistas deben ser renderizadas independientemente y en orden:

  1. Registro del usuario (customer_component.ex)
    Register Customer

  2. Registro del vehículo (vehicle_component.ex)
    Register Vehicle

Una vez registrado el usuario, se le debe pasar el id del usuario al componente de vehículo, y así poder asociar exitosamente el usuario con el vehículo cuando estamos registrando el vehículo.

Cómo logramos esto? Vamos al código...

La lógica condicional la manejamos en la vista padre de los componentes usuario y vehículo, en este caso será la LiveView vehicle_live/index.ex y su respectivo archivo vehicle_live/index.html.leex donde se encuentran los elementos HTML a renderizar.

En el archivo vehicle_live/index.ex:

defmodule CarWorkshopWeb.VehicleLive.Index do
  use CarWorkshopWeb, :live_view

  alias CarWorkshop.{Vehicles.Vehicle, Accounts.Customer}

  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket, customer: %Customer{}, view_to_show: :customer_view)}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :new, _params) do
    socket
    |> assign(:page_title, "Register Vehicle")
    |> assign(:vehicle, %Vehicle{})
    |> assign(:customer, %Customer{})
  end

  @impl true
  def handle_info({:customer_registered, customer, view_to_show}, socket),
    do: {:noreply, assign(socket, customer: customer, view_to_show: view_to_show)}
end
Enter fullscreen mode Exit fullscreen mode

En la función mount, le asignamos al socket la propiedad view_to_show por medio de la cual sabremos el componente a renderizar en vehicle_live/index.html.leex. Le damos un valor de :customer_view, haciendo que la primera vista en ser renderizada sea la del componente de usuario.

La función callback handle_info será la encargada de cambiar la propiedad view_to_show del socket, y así, se realizará el renderizado condicional de cada componente según los argumentos que le pasemos a esta función.

En el archivo vehicle_live/index.html.leex:

<%= if @view_to_show == :vehicle_view do %>
  <%= live_component @socket, CarWorkshopWeb.VehicleComponent,
    id: @vehicle.id || :new,
    title: "Register Vehicle",
    action: @live_action,
    vehicle: @vehicle,
    customer_id: @customer.id
  %>
<% else %>
  <%= live_component @socket, CarWorkshopWeb.CustomerComponent,
    id: @customer.id || :new,
    title: "Register Customer",
    action: @live_action,
    customer: @customer
  %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Con esto, ya tenemos renderizada la vista de usuario. Lo único que nos faltaría sería llamar al callback handle_info desde el componente de usuario una vez que se haya ejecutado toda nuestra lógica de negocio, y así, luego permitir el renderizado de la vista de vehículo.

Supongamos que queremos renderizar el componente de vehículo inmediatamente después que se registre el usuario exitosamente. Para esto, en el archivo customer_component.ex:

  ...
  @impl true
  def handle_event("save", %{"customer" => customer_params}, socket) do
    case Accounts.create_customer(customer_params) do
      {:ok, customer} ->
        send(self(), {:customer_registered, customer, :vehicle_view})

        {:noreply, socket}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, changeset: changeset)}
    end
  end
  ...
Enter fullscreen mode Exit fullscreen mode

Usando la función send() hacemos que en el LiveView padre se ejecute el callback handle_info que cumple con el contrato de parámetros {:customer_registered, customer, view_to_show}. Del cual podremos saber el indentificador del handle_info a ejecutar (:customer_registered), el usuario recién creado (customer) y la vista a renderizar (:vehicle_view).

Esto es todo, tenemos renderizado condicional de componentes!


Para ver la implementación completa puedes visitar el repo:

GitHub logo santiagocardo / car-workshop

Car Workshop Managment Web App

CarWorkshop

To start your Phoenix server:

  • Install dependencies with mix deps.get
  • Create and migrate your database with mix ecto.setup
  • Install Node.js dependencies with npm install inside the assets directory
  • Start Phoenix endpoint with mix phx.server

Now you can visit localhost:4000 from your browser.

Ready to run in production? Please check our deployment guides.

Learn more

Top comments (0)