christhekeele
Hello! I’m trying to implement this logic in a hologram component:
let seconds_from_elixir = some_integer;
const longFormatter = new Intl.DurationFormat("en", { style: "long" });
return longFormatter.format({seconds: seconds});
What I have tried
I assumed this would work, but it cannot find Intl.DurationFormat:
defp human_readable_time(seconds) do
:"Intl.DurationFormat"
|> JS.new(["en", style: "long"])
|> JS.call(:format, seconds: seconds)
end
results in:
Uncaught HologramInterpreterError:
there is no JS binding or global named "Intl.DurationFormat" in scope
Additionally, I did not think this would work, but tried it anyways (resulting in an empty string):
defp human_readable_time(seconds) do
JS.exec("""
const formatter = new Intl.DurationFormat("en", { style: "long" });
return formatter.format({seconds: #{seconds}});
""")
end
Trending in Questions
Hi all,
I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
I’m very new to Elixir, so sorry if this is a silly question:
I am wondering what happens to the mailbox of a process when the machine i...
New
How do I tell Hologram not to escape HTML when substituting into templates?
{Textile.render(@product.long_description)}
escapes the HTM...
New
Background
When trying to execute mix release on a Windows 11 machine for a Phoenix project I get the following error:
* assembling mark...
New
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
Is there a way to import record definitions in the iex shell for pretty printing (similar to the rr command in the erl shell)? This woul...
New
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used?
I’ve got two Github accoun...
New
Other Trending Topics
Hey there,
I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first.
...
New
Hi everyone :waving_hand:
I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly)
AI dream has many faces, but general...
New
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #code-sync
- #discussion
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #ai
- #umbrella
- #elixirconf-us
- #full-time-contract
- #podcasts-by-brainlid
- #blog-post
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #elixirconf-eu
- #processes
- #security
- #api
- #forms
- #performance










Showing Posts 1 to 4- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
bartblast
Hey Chris, the thing that trips Elixir folks up here is that
Intl.DurationFormatlooks like a module name, but it isn’t one. In Elixir,Intl.DurationFormatis a single atom and the dots are part of the name. In JS,Intlis a global object, think of a map sitting in a global variable, and.DurationFormatreads a key off it at runtime. That key holds a class.newmakes an instance of the class, andformatis a method you call on that instance.Hologram resolves an atom as one global name, so
:"Intl.DurationFormat"looks for a global called “Intl.DurationFormat”, dot included, and there’s none. Each JS step gets its own call instead:The options have to be maps. A JS object is the closest thing to an Elixir map, and JS has no keyword lists.
["en", style: "long"]is["en", {:style, "long"}], and tuples become JS arrays, so the constructor got["style", "long"]instead of{style: "long"}. Same forseconds: seconds, which reachedformatas["seconds", seconds].Your
JS.execversion works.Intl.DurationFormatformats a zero duration as an empty string, sosecondswas probably 0.Where you call it matters too. Call it from an action, not straight from the template. Templates also render on the server, and there every
JS.*call does nothing and returns:ok, so the HTML the server sends would say “ok” in that spot. Format the string in the action that changesseconds, put it in state, and render the state:Then the template just shows
{@duration}.It’s not a bug, but writing the name the way JS writes it would be nicer, so I filed an issue for dotted names:
JS interop can’t name a namespaced global like Intl.DurationFormat (#1550)
christhekeele
This worked, thanks!
My situation here took me a second to figure out. Hopefully that means I’m getting better at hologram! In my case:
secondsis a prop on the component, passed down from a parent componentSo, there won’t always be a client-side action to trigger re-render over the server-side one.
Initially, I tried to calculate them as a derived state in
init/2as an on-mount-only thing, but ofc that’s not called becauseinit/3is instead. Eventually I found the docs that explain the idiomatic way to get on-mount behaviour in server-rendered components is to enqueue an action from the server side, very slick.This makes sense. I can see where my Elixir-writing mental model diverged from the JS reality long enough to be confused, but being informed about the right way now, honestly I’m not 100% convinced supporting
:Intl.DurationFormatshould be preferred over operating on the right mental model, and perhaps a note in the JS interop guide. Your call ofc!Sure enough, I ran into this before reading your full answer, rushing off to try out the right way! Off the cuff, this is a strong smell to me that
JS.callshould return{:ok, result} | {:error, :runtime_unavailable}or some such: that way the caller could choose to be defensive about noticing such issues by matching on the result if it chose. Maybe there’s another more compelling reason for the API as-is though?bartblast
Glad it worked
If what you need is a value that keeps updating, like a running timer, you can queue a tick from
init/3and have the tick action queue itself again with a delay:I’ll keep the dotted names issue, since that’s how every JS example spells it, and the docs will cover the mental model too.
As for tagged tuples, interop only runs in code reached from actions, and actions run in the browser. So in supported code there’s no “unavailable” case to match on, and tuples would break piping one call into the next. Template calls are the gap, and that question is open in Question: is JS interop in render-reachable functions (with the server-side :ok fallback) supported? · Issue #1409 · bartblast/hologram · GitHub
christhekeele
That’s exactly what I’m doing, just a few components up! The one that displays it receives the elapsed seconds as a prop.