We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Use `Integer.parse/1` for params, not `String.to_integer/1`
almirsarajcic
Phoenix does zero automatic type coercion. Every value in params arrives as a string — or not at all. Reaching for String.to_integer/1 on user-supplied params is a crash waiting to happen in two different ways.
# ❌ Crashes on bad input AND on missing keys
def handle_event("paginate", params, socket) do
page = String.to_integer(params["page"]) # raises ArgumentError on "abc"
# raises FunctionClauseError on nil
{:noreply, assign(socket, page: page)}
end
# ✅ Handles both gracefully
def handle_event("paginate", params, socket) do
case parse_integer(params["page"]) do
{:ok, page} -> {:noreply, assign(socket, page: page)}
:error -> {:noreply, socket}
end
end
Plug.Conn.Query decodes every query parameter as a string. When a key is absent entirely, params["page"] returns nil, and String.to_integer(nil) raises ** (FunctionClauseError) no function clause matching in String.to_integer/1 — a different crash from the ArgumentError you’d get on "abc". Both are unhandled exceptions in production.
Integer.parse/1 is the right tool, but it has a gotcha you need to account for:
Integer.parse("123") # {123, ""} — success
Integer.parse("-42") # {-42, ""} — negative numbers work
Integer.parse("abc") # :error
Integer.parse("") # :error
Integer.parse(" 123") # :error — leading whitespace fails
Integer.parse("123abc") # {123, "abc"} — partial parse, NOT :error
That last case is the one that bites people. "123abc" does not return :error — it returns a tuple with a non-empty remainder. Matching only on {n, ""} rejects it correctly:
@spec parse_integer(String.t() | nil) :: {:ok, integer()} | :error
def parse_integer(value) when is_binary(value) do
case Integer.parse(value) do
{n, ""} -> {:ok, n}
_ -> :error
end
end
def parse_integer(_), do: :error
The second clause catches nil and any other non-string value without an explicit nil check. The _ branch in the case handles both :error and partial parses like {123, "abc"} in one shot — no need to enumerate them separately.
A few things worth keeping in mind:
- Leading whitespace fails silently —
Integer.parse(" 123")returns:error, so if you’re dealing with form inputs that might have whitespace,String.trim/1before parsing is appropriate String.to_integer/1is not always wrong — it’s fine when the value is guaranteed to be a well-formed integer string, such as an ID coming from your own database query or a compiled route parameter. It’s user-supplied params where it’s dangerousInteger.parse/1accepts any radix —Integer.parse("ff", 16)returns{255, ""}, which is handy for things like color parsing
copied to clipboard