clojurescript 2025-06-22

Hey, I'm pretty new to ClojureScript and been trying to port a little project over from Javascript to use Replicant but getting stuck on a function to "remap" some of my data that is in a format like: [{:id 1 :tags [:abc]} {:id 2 :tags [:abc :xyz]} {:id 3 :tags [:xyz]} ] to something like this {:abc [{:id 1 :tags[:abc]} {:id 2 :tags [:abc :xyz]}] :xyz [{:id 2 :tags[:abc :xyz]} {:id 3 :tags [:xyz]}] } I've got it working in Javascript but spent the whole afternoon trying to get something working the same in cljs but can't figure it out, any help?

You basically want to build an index of tag->items, right?

(def data [{:id 1 :tags [:abc]}
           {:id 2 :tags [:abc :xyz]}
           {:id 3 :tags [:xyz]}])
Here's an approach that first finds all the unique tags, then builds the index:
(->> (set (mapcat :tags data))
     (map (fn [tag]
            [tag (filter #(<= 0 (.indexOf (:tags %) tag)) data)]))
     (into {}))
If you'd kept the tags in sets instead it would've been a little easier:
(let [data [{:id 1 :tags #{:abc}}
            {:id 2 :tags #{:abc :xyz}}
            {:id 3 :tags #{:xyz}}]]
  (->> (set (mapcat :tags data))
       (map (fn [tag]
              [tag (filter #((:tags %) tag) data)]))
       (into {})))
You could also use reduce:
(reduce
 (fn [acc item]
   (reduce (fn [acc* tag]
             (update acc* tag conj item)) acc (:tags item)))
 {} data)

Ah the man himself! Loving Replicant btw

Happy to hear it 😊

Just deciphering your message, but one thing that I dont really understand is, does it make sense for :tags to use :a vector of [:abc :xyz] or would strings be better, I dont really understand what :abc really means in clojure, is that a keyword?

It's a keyword. Keywords are used to name/identify things in ClojureScript. They're similar to strings, but somewhat more restricted in what they can represent (you wouldn't create a keyword with a space in it). Keywords are "interned", meaning that wherever you type :abc you get the same exact object back:

(identical? :a (keyword (str/lower-case (str/upper-case "a")))) ;;=> true
(identical? "a" (str/lower-case (str/upper-case "a")))          ;;=> false

šŸ™Œ 1

Whether it makes sense to use them for tags depends. If the tags are a limited enumerated set of keywords you maintain in your code, then keywords are a good fit. If tags are user generated data, then strings would probably be better.

I try to stay away from programatically constructing keywords from user input, if that makes sense.

Ah okay, thanks so much for your help. That first example works with my existing data structure but I'll probably take your feedback on and look at using a set and probably replace with strings instead of keywords

Regardless of strings/keywords, a set would be good here, you don't want items to have duplicate tags šŸ‘

šŸ‘ 1

yeah thanks for the advice and help. I thought I understood a bit of clojure but its a bit trickier than I thought. Was stuck on that for so long and AI really didn't help. Another question if you've got the time. Is ->> threading macro right, could that be replace with a transducer?

->> is a threading macro yes. It can not be replaced with a transducer. The threading macro is a pure syntactical thing. Take my example above. If you'd just write it out it would look like:

(into {} (map (fn [tag] [tag (filter #((:tags %) tag) data)]) (set (mapcat :tags data))))
We can reformat this a little:
(into {}
      (map (fn [tag] [tag (filter #((:tags %) tag) data)])
           (set
            (mapcat :tags data))))
This is evaluated from innermost expression to outermost. So first we evaluate (mapcat :tags data):
(into {}
      (map (fn [tag] [tag (filter #((:tags %) tag) data)])
           (set
            tags)))
Then we evalulate (set tags), then the (map ,,,) and finally (into {} ,,,). As you can see, in all of these calls, the evaluated expression goes in the outer expression as the last argument. The thread last macro allows you to write this in a more readable way. Here it is step by step:
(->> (map (fn [tag] [tag (filter #((:tags %) tag) data)])
          (set (mapcat :tags data)))
     (into {}))
(->> (set (mapcat :tags data))
     (map (fn [tag] [tag (filter #((:tags %) tag) data)]))
     (into {}))
(->> (mapcat :tags data)
     set
     (map (fn [tag] [tag (filter #((:tags %) tag) data)]))
     (into {}))
(->> data
     (mapcat :tags)
     set
     (map (fn [tag] [tag (filter #((:tags %) tag) data)]))
     (into {}))
Exactly where you stop is a matter of taste.

ā¤ļø 2

Oh wow thanks so much for the detailed break down, really appreciate it.

šŸ‘ 1

Hey! Long time Clojure/ClojureScript/ClojureCLR dev who's planning on spinning up a couple local political campaign sites in ClojureScript. I'm wondering what the easiest way to write a simple web page is assuming the only thing I need it to do beyond routing and providing information is allowing donations (probably through ActBlue which can just be an iframe element on the page I assume) and an email submission form. Is something like https://github.com/lilactown/helix overkill?

What do you mean by "reactive"? Given the rest of the description, I wouldn't even use CLJS, probably. Just a good old multi-page HTML website.

Yeah on second thought it may not even be reactive given there probably aren't state updates

Which would be closer to just writing straight HTML

I think most of the work for the sites will be CSS to make the page look good on different devices

@p-himik removed "reactive" from the description because I think you're right

One other thought though was just that I wanted something reusable, I keep finding myself volunteering to do local political sites and it feels like I should just have a template to speed things up, which I think something like cryogen could help with?

Probably! Haven't used it myself. There are also plenty of HTML templates online that are either free or cost next to nothing. If there's no compelling reason to avoid HTML, you could simply grab the most suitable template, fill in the data and be done with it.

ā¤ļø 1

As an example, I'm still using one of their templates: https://pixelarity.com/

Tailwind tends to be polarizing but if you are ok with using it, HTML + Tailwind gets you started quickly, and there are many components out there that you can just reuse. If you prefer writing clojure and want to retain the ability to manipulate the markup as data, perhaps just a thin layer of hiccup would do. Hiccup combines nicely with Tailwind, too, and there's a http://html2hiccup.buttercloud.com/Hiccup> converter out there.

ā¤ļø 1

I’m liking https://github.com/cjohansen/powerpack lately for this type of website

ā¤ļø 1

helix is great I highly recommend it, or pure https://github.com/squint-cljs/squint/tree/main/examples/vite-react if you feeling adventurous (is that a word? haha)