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: