Fork me on GitHub
David Mays06:11:46

Wondering if someone could guide me through creating your own component in Biff? For example, adding a custom "label" rum component, based off of the first example in the Rum documentation. When I add a component using (rum/defc label) the editor gives me a warning from clj-kondo about an unresolved symbol, or gives the option to create a function.


What if you just try

(defn label [text]
  [:div {:class "label"} text])
and then call it later in your home function as
(label "my text)

David Mays13:11:38

Works perfect, thanks! And you solved my issue in my next example, where I had wrapped my input text in square brackets unnecessarily. It seems like by defining my "components" as functions like your example provides all the functionality I'd really need, in what scenarios (if any) would you want / need to use (rum/defc to create a custom component? It seems this is creating a react component?

Jacob O'Bryant15:11:37

It's useful if you're defining components on the frontend (I.e. in cljs). In that case, defc does create a react component. With server side rendering, defc is basically the same as defn. I'm honestly not sure if there's any difference. (I guess one difference I am aware of: if you're making components that are shared between your clj and cljs code, then besides letting you use the same code, defc can help rum insert metadata on the backend which will help the frontend do hydration). basically, for biff apps, you can just stick with defn :).

David Mays06:11:25

Also how to add a generic HTML generating function that includes parameters? (Not a custom component, but just a function generating HTML). An example in the attached snippet is my attempt to add a function called myHtmlFunction after signin-form in home.clj. This builds with no errors, but doesn't display anything in the browser if I try and have an 'input text' parameter. Does render if just hard coded though, Sorry for the dumb questions!


You can just call your non-hard-coded myHTMLFunction with

(myHtmlFunction "Hello World!")
as you do not need the square brackets around the argument when calling the function. Your hard coded version did show up as you are not supplying an argument (and therefore not using the square brackets around it that won’t work). Putting brackets around the argument is actually calling the function with a vector containing a string when here you just want to supply a string. In all situations, though, best to put a space between your function name and the argument. If you did want to call a function with an argument of a string in a vector it would look like
(my-function ["yay"])


Minor style point: typically, Clojure functions are named with dash-seperated-lowercase-words (“kebob case”) so your function would be

(defn my-html-function [inputText]
  [:div {} inputText])
and called the same way
(my-html-function "Hello World!")

David Mays12:11:52

Thanks so much! That was very helpful. I know these are the super basics, but having shifted a paradigm from from oop to functional, client to server side, and html to rum syntax is enough change to make the tiny things a bit overwhelming, and cracking the door open enough to experiment with your help is much appreciated.

🎅 1
👍 1

Does any of this look off at first glance?

(defn wrap-signed-in [handler]
  (fn [{:keys [session] :as req}]
    (if (some? (:uid session))
      (handler req)
      (fn [{:keys [biff/db session] :as req}]
        (if-some [user (xt/pull db
                                '[* {(:mem/_user {:as :user/mems})
                                     [* {:mem/comm [*]}]}]
                                (:uid session))]
          (handler (assoc req :user user))
          {:status 303
           :headers {"location" "/"}})))))
My (community… function in app.clj in the new module of the tutorial is getting a nil user from req when trying to biff/pprint it.


you're returning a middleware function inside a middleware function; I think you just want to slurp the contents of that second function into the else part of your if form, unless I'm not understanding what you're trying to do. the xt/pull call looks weird, but I haven't used it directly; I always use biff/q or biff/lookup . As for 'isolating' it, I would extract the function, and pass it the db as an arg. For testing, though, you can dereference the biff/system atom. something like:

(defn user-by-session-uid [{:keys [biff.xtdb/node]} session-uid]
  (let [db (xt/db node)]
    (biff/lookup db :mem/session-uid session-uid)))

(defn wrap-signed-in [handler]
  (fn [{:keys [session] :as req}]
    (if-let [session-uid (:uid session)]
      (let [user (user-by-session-uid req session-uid)]
        (handler (assoc req :mem/user user)))
      {:status 303
       :headers {"location" "/"}})))

  (let [sys @biff/system
        _ (tap> sys)
        fake-req (assoc sys :session {:uid "hardcoded-uid"})
        fake-handler (fn [{:keys [mem/user]}]
                        [:div#user user]))
        middleware-fn (wrap-signed-in fake-handler)]
    (middleware-fn fake-req))

☝️ 1

btw, I recommend the pattern of putting code to exercise the function(s) you're writing in a comment and sending self-contained forms to the repl; (Ctrl-x-e in emacs and iirc intelliJ). is also super useful for seeing what is inside datastructures like biff/system (`tap>` sends to anything that registers as a tap)

Jacob O'Bryant16:11:50

from taking a look at the diff on, it looks like you added the new/green-highlighted code but didn't delete the old/red-highlighted code

Jacob O'Bryant16:11:53

(the xt/pull call is discussed briefly in the tutorial btw--it's handy when you already have the document ID and you want to use the pull syntax to get additional referenced documents)

Jacob O'Bryant16:11:09

also for debugging/isolating the pull stuff, I often do it in repl.clj (see the let form that calls get-sys, which is kinda handy since then the db is right there. though you may have to add requires to that file, in which case adding a comment form to the original namespace you're working on can be more convenient.


didn't know about get-sys- thanks 🙂

👌 1

You all rock! Thank you so much. This discussion hit so many little issues I was having and it’ll allow me to hit cruising altitude with my workflow. 🙂 I have to implement and play with it all and so there may be a few little follow on questions here.

👍 1

For the record, lol… I actually had a non middleware-inside-middleware version of wrap-signed-in up until I flailed and somehow modified it as I posted it at 3:30 am… but I’m glad I did as this thread has been really helpful. “Suuuuure, Macro Bartfast… if you say so.” I’m not sure what the issue was but something was wrong with whatever I had previously, that’s for sure. It’s all working now. I’ll take a crack at working with the db call in isolation next.

😆 1

And I’m also going to set up Portal. Pumped on that.


As a side question/issue, I wanted to isolate the pull somewhere (repl.clj?) but am having a hard time figuring out how to supply the db to it (assuming my idea that isolating the pull would work is correct and is a good troubleshooting step).


(for anyone seeing this question… see my previous question and the comprehensive answers which addressed this.)