Managing State: LiveView vs React

I’ve been learning more about LiveView function Components and LiveComponents from the Programming Phoenix LiveView book.

I was curious how these compared to React Function and Class components.

The concepts seem similar, so wanted to see the similarities and differences in managing state in React and LiveView.

I started with the React Docs section on Lifting State Up.

The React docs use a temperature calculator that calculates whether the water would boil at a given temperature. The requirements include keeping a Celsius input and a Fahrenheit input in sync.

The final React code is on CodePen.

Using the LiveView Managing State docs, I created the same app using LiveView.

Here are the LiveView modules if you are interested in how they compare. I’m also curious if anyone would have done anything differently.

defmodule Temperature do
  defstruct [:value, :scale]

  def would_boil?(%{value: ""}), do: false

  def would_boil?(%{scale: :celsius} = temperature) do
    String.to_integer(temperature.value) >= 100
  end

  def would_boil?(%{scale: :fahrenheit} = temperature) do
    String.to_integer(temperature.value) >= 212
  end

  def try_convert(%{scale: scale, value: value}, scale), do: value

  def try_convert(temperature, to_scale) do
    case Float.parse(temperature.value) do
      :error ->
        ""

      {value, _} ->
        value
        |> convert(to_scale)
        |> Float.round(3)
        |> Float.to_string()
    end
  end

  defp convert(fahrenheit, :celsius) do
    (fahrenheit - 32) * 5 / 9
  end

  defp convert(celsius, :fahrenheit) do
    celsius * 9 / 5 + 32
  end
end
defmodule TemperatureWeb.CalculatorLive.Component do
  use Phoenix.Component

  def boiling_verdict(assigns) do
    ~H"""
    <%= if Temperature.would_boil?(@temperature) do %>
      <p>The water would boil.</p>
    <% else %>
      <p>The water would not boil.</p>
    <% end %>
    """
  end
end
defmodule TemperatureWeb.CalculatorLive.Form do
  use TemperatureWeb, :live_component

  def handle_event("change", %{"temperature" => temperature_params}, socket) do
    temperature = %Temperature{value: temperature_params["value"], scale: socket.assigns.scale}

    send(self(), {:updated_temperature, temperature})
    {:noreply, socket}
  end

  def render(assigns) do
    ~H"""
    <fieldset>
      <legend>Enter temperature in <%= @scale %>:</legend>
      <.form
        let={f} 
        for={:temperature} 
        phx-change="change" 
        phx-target={@myself}
      >
        <%= number_input f, :value, value: Temperature.try_convert(@temperature, @scale) %> 
    </.form>
    </fieldset>
    """
  end
end
defmodule TemperatureWeb.CalculatorLive do
  use Phoenix.LiveView, layout: {TemperatureWeb.LayoutView, "live.html"}
  use Phoenix.HTML

  alias __MODULE__.Component
  alias __MODULE__.Form

  def mount(_params, _session, socket) do
    {:ok, assign(socket, temperature: %Temperature{value: "0", scale: :celsius})}
  end

  def handle_info({:updated_temperature, temperature}, socket) do
    {:noreply, assign(socket, :temperature, temperature)}
  end

  def render(assigns) do
    ~H"""
    <div> 
      <.live_component module={TemperatureWeb.CalculatorLive.Form}
        id="celsius-form" 
        temperature={@temperature}  
        scale={:celsius}/>

      <.live_component module={TemperatureWeb.CalculatorLive.Form}
        id="fahrenheit-form" 
        temperature={@temperature}  
        scale={:fahrenheit}/>

      <Component.boiling_verdict temperature={@temperature} /> 
    </div>
    """
  end
end

Here is what the app looks like:

1 Like

If all the component is doing is sending the event to the liveview, I would handle the event in the liveview. You can pass the scale back by creating a hidden input for scale in the form. Then the component does not need to be live.

1 Like

LiveView’s function and live components are more closely related to React’s pattern of “dumb” and “smart” components aka or “view” and “container” aka… actually, I forget the other names people use.

React’s function and class components are just two different ways to accomplish the same thing. Both can either hold state or not. You can can go all in on either one and still use the smart/dumb pattern.

LiveView function components are not analogous to React function components since LiveView’s do not give you a way to store state—they are pure functions. React function components give you state via hooks. The React analogue to a LV functional component would be either a function component that doesn’t call a hook or a class component that doesn’t store any state (and in both cases, have no other side-effects of any kind).

A LiveComponent can be easily construed as a React class component since it wraps up a view and functionality in a module. However, you have to remember that Elixir doesn’t store state in modules, it stores it in processes. For this reason, LiveComponents are actually not like class components at all and more like a React function component packaged up with its callbacks in a module using hooks for state (in this example, hooks are like processes if you squint really, really hard, but don’t tell anyone I said that).

To take it even further, LiveView also has the LiveView itself! This is where my mind starts to bend trying to draw analogues. For example, you can have nested LiveViews, so in that way a LiveView itself is kind of like a LiveComponent, only it’s not. I’m going to stop there, though, since I’m tired, tipsy, and if I write the word “function” or “component” again tonight, my head might explode.

2 Likes