wktdev

wktdev

How do I read Phoenix errors?

I’m playing a game where I intentionally create errors in my programs to see if I can fix them based on console output alone.

Cliff notes answer: I can’t

In JS I feel like errors are more direct and I can fix a program by finding the file and line number, and working backwards.

In Elixir I wouldn’t be able to fix a codebase that is handed to me from another developer without the help of forums and Google.

So for example, this error below is the result of me changing a function argument.

I went from this:

  def handle_event("send", %{"text" => text}, socket) do     # This is what I changed below
    IO.inspect text
    Phoenix.PubSub.broadcast(App.PubSub, topic, {:text_stuff, text}) 
    {:noreply, socket}
  end

To this

  def handle_event("send", text, socket) do     # This is what I changed 
    IO.inspect text
    Phoenix.PubSub.broadcast(App.PubSub, topic, {:text_stuff, text}) 
    {:noreply, socket}
  end

The code is part of a larger Live View (below)

I would like to know how to use the errors to work backwards fix this. If anyone wants to share their thought process I would like to read it.

Error

`[error] GenServer #PID<0.1420.0> terminating
** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{"text" => "ad"} of type Map. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Float, Integer, List, NaiveDateTime, Phoenix.HTML.Form, Phoenix.LiveComponent.CID, Phoenix.LiveView.Component, Phoenix.LiveView.Comprehension, Phoenix.LiveView.JS, Phoenix.LiveView.Rendered, Time, Tuple, URI
    (phoenix_html 3.3.2) lib/phoenix_html/safe.ex:1: Phoenix.HTML.Safe.impl_for!/1
    (phoenix_html 3.3.2) lib/phoenix_html/safe.ex:15: Phoenix.HTML.Safe.to_iodata/1
    (app 0.1.0) lib/app_web/live/send_live.ex:38: anonymous fn/2 in AppWeb.SendLive.render/1
    (phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:375: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:373: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:139: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.18.18) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.18.18) lib/phoenix_live_view/channel.ex:689: Phoenix.LiveView.Channel.handle_changed/4
    (stdlib 4.2) gen_server.erl:1123: :gen_server.try_dispatch/4
    (stdlib 4.2) gen_server.erl:1200: :gen_server.handle_msg/6
    (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {:text_stuff, %{"text" => "ad"}}
State: %{components: {%{}, %{}, 1}, join_ref: "4", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<id: "phx-F3-xORYdOS-9swdH", endpoint: AppWeb.Endpoint, view: AppWeb.SendLive, parent_pid: nil, root_pid: #PID<0.1420.0>, router: AppWeb.Router, assigns: %{__changed__: %{}, flash: %{}, live_action: :home, message_item: ""}, transport_pid: #PID<0.1413.0>, ...>, topic: "lv:phx-F3-xORYdOS-9swdH", upload_names: %{}, upload_pids: %{}}`

Full LIve VIew Page

defmodule AppWeb.SendLive do
  use AppWeb, :live_view
  

  def mount(_params, _session, socket) do

    if connected?(socket) do
      Phoenix.PubSub.subscribe(App.PubSub, topic)    
    end

    {:ok, assign(socket, message_item: "")}
  end

    def handle_info({:text_stuff, text}, socket) do 
    {:noreply, assign(socket, message_item: text)}
  end

  def handle_event("send", %{"text" => text}, socket) do     # This is what I changed 
    IO.inspect text
    Phoenix.PubSub.broadcast(App.PubSub, topic, {:text_stuff, text}) 
    {:noreply, socket}
  end
  
  defp topic do #Topic
    "message"
  end

  def render(assigns)do   
   ~H"""
    <div>
      <h1>Send Message</h1>
      <form phx-submit="send">
        <input type="text" name="text" />
        <button type="submit">Send</button>
      </form>
     <div>
      <h1>ChatLive</h1>
      <%= @message_item %>
      </div>

  
    </div>

    """
  end
end



Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Sure! So when I read this I see that the top line is:

** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{"text" => "ad"} of type Map. 

%{"text" => "ad"} sure looks like data from my app, so somewhere I’m giving Phoenix some data that isn’t HTML.Safe. Looking at the stack trace, the first few lines are in Phoenix code, but then I see

    (app 0.1.0) lib/app_web/live/send_live.ex:38: anonymous fn/2 in AppWeb.SendLive.render/1

That’s code from my app, so let’s go check it out. Line 38 is

<%= @message_item %>

This is where I’m injecting the @message_item assign into the HTML. The error says that it isn’t HTML safe, so basically it’s telling me it isn’t allowed to just plop an Elixir map into HTML. Looking at my code I can see where I set message_item which is just:

    def handle_info({:text_stuff, text}, socket) do 
      {:noreply, assign(socket, message_item: text)}
    end

So somehow text is actually %{"text" => "ad"}. From there I’d look and see at what sends {:text_stuff and so on.

kokolegorille

kokolegorille

If You are not sure, You can always always render with…

<%= inspect @message_item %>
benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yeah Phoenix.HTML.Safe — Phoenix.HTML v4.3.0 is a bit terse but it does at least say:

In order to promote HTML safety, Phoenix templates do not use Kernel.to_string/1 to convert data types to strings in templates. Instead, Phoenix uses this protocol which must be implemented by data structures and guarantee that a HTML safe representation is returned.

The key bit is “to convert data types to strings in templates”. So if you get a ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for error then that means it couldn’t convert it to a string in a template.

Where Next?

Popular in Questions Top

New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

Other popular topics Top

hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44167 214
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement