This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-05-17
Channels
- # ai (1)
- # announcements (1)
- # aws (38)
- # babashka (25)
- # beginners (84)
- # biff (11)
- # calva (58)
- # clerk (14)
- # clj-kondo (14)
- # cljdoc (9)
- # cljs-dev (2)
- # clojars (2)
- # clojure (93)
- # clojure-czech (2)
- # clojure-dev (13)
- # clojure-europe (19)
- # clojure-nl (1)
- # clojure-spec (13)
- # clojure-uk (2)
- # clojurescript (6)
- # conjure (1)
- # core-async (9)
- # cursive (12)
- # data-science (7)
- # datahike (47)
- # datalevin (10)
- # datalog (3)
- # datomic (35)
- # emacs (3)
- # events (4)
- # fulcro (49)
- # gratitude (7)
- # humbleui (1)
- # hyperfiddle (42)
- # jobs-discuss (19)
- # kaocha (5)
- # lsp (20)
- # malli (3)
- # meander (2)
- # membrane (2)
- # off-topic (22)
- # pathom (2)
- # polylith (14)
- # practicalli (1)
- # rdf (3)
- # reitit (2)
- # shadow-cljs (11)
- # squint (3)
- # tools-deps (32)
- # vim (9)
- # xtdb (16)
Has anyone compared Fulcro to Electric? https://github.com/hyperfiddle/electric Both take novel approaches to web development. Both are different enough from traditional approaches that comparing their engineering trade-offs is challenging.
I plan to rewrite a small Fulcro app in Electric but it will take me many weeks to get to it 😭 My impressions is > To me, the most interesting part is that Phoenix, similarly to Electric Clojure and HTMX, moves state and logic to the backend (BE) and handles efficiently communicating changes to the (mostly?) static frontend (FE). i.e. both F. and E. handle server<>client communication but Fulcro is for SPAs and other frontend-code-heavy apps, while Electric makes it possible to have most logic and state management on the backend. But I might be wrong…
After going through the troubleshooting blog post, I feel I'm somewhere in the realm of 3: Data in DB OK but props in the UI are nil, but I'm not seeing where I'm going wrong. https://www.loom.com/share/74badf7a675d428a83b6eb0782cadc8d
target a FIELD, not a COMPONENT. The ident itself will merge components to their correct locations. You want load to place an ident in the field.
So, you mean for RecipeDetail to be another view of a given recipe. The ident of that component should therefore be a recipe’s ident (not some singleton component). Normalization will always put a thing at it’s ident, but you’re asking load to normalize a recipe using a thing that won’t have a recipe ident.
Yes, I'm trying to get this RecipeList -> Recipe Detail flow. Partial load on the list query, then full record load on detail.
So, what you want is a recipe list, which should a summary of recipes. Then a recipe detail that shows that same info expanded.
so:
(defsc RecipeDetail [this props]
{:ident :recipe/id
:query [:recipe/id :recipe/description :recipe/name]
; no initial state...this is something you'll load, so won't be on first render frame anyway
:route-segment ["recipe-detail" :id]
:will-enter (fn [app {:keys [id]}]
(let [id (coerce id)] ; you do coercion
(dr/route-deferred [:recipe/id id]
#(df/load app [:recipe/id id] RecipeDetail)))
}
...)
(defsc RecipeRow [this props]
{:ident :recipe/id
:query [:recipe/id :recipe/name]
; no initial state...this is something you'll load, so won't be on first render frame anyway
}
...)
you only make up singleton idents for UI-only components that have no individual identity otherwise.
Now, your “report” of all recipes is just what you have in Main (which I would call RecipeList)
when a user click on a particular recipe, then you want to issue a route to a RecipeDetail (which would go in the router as a target) with an id.
now your will-enter will have that parameter (as a string…you’ll have to convert it back to the correct type), and you can issue your load. Alternatively, you could issue the load, and as a post-action issue your route change
I think I am following up to ["recipe-detail" :id]. This is what I have so far. Please excuse the strange naming :recipes/id instead of :recipe/id the postgres db has pluralized tables.
(ns com.sajb.ui
(:require
[com.sajb.mutations :as mut]
[com.fulcrologic.fulcro.mutations :as m :refer [defmutation]]
[com.fulcrologic.fulcro.routing.dynamic-routing :as dr :refer [defrouter]]
[com.fulcrologic.fulcro.algorithms.merge :as merge]
[com.fulcrologic.fulcro.algorithms.tempid :as tempid]
[com.fulcrologic.fulcro.algorithms.data-targeting :as targeting]
[com.fulcrologic.fulcro.algorithms.normalized-state :as norm]
[com.fulcrologic.fulcro.components :as comp :refer [defsc transact!]]
[com.fulcrologic.fulcro.ui-state-machines :as uism]
[com.sajb.application :refer [main-app]]
[com.fulcrologic.fulcro.raw.components :as rc]
[com.fulcrologic.fulcro.data-fetch :as df]
[com.fulcrologic.fulcro.dom :as d]
[reitit.frontend.easy :as rfe]))
(declare RecipeDetails)
(defmutation load-recipe [{:recipes/keys [id]}]
(action [{:keys [app]}]
(df/load! app [:recipes/id id] RecipeDetails)))
(defmutation use-recipe [{:recipes/keys [id]}]
(action [{:keys [app state]}]
(swap! state assoc-in [:component/id ::recipe-details] [:recipes/id id])
(dr/target-ready! app [:component/id ::recipe-details])))
(defsc RecipeDetails [this {:as props
:recipes/keys [id name description]}]
{:ident :recipes/id
:query [:recipes/id :recipes/name :recipes/description]
:route-segment ["recipes" :recipes/id]
:will-enter (fn [app {:recipes/keys [id]}]
(comp/transact! app [(use-recipe {:recipes/id id})])
(dr/route-deferred [:component/id ::recipe-details]
#(comp/transact! app [(load-recipe {:recipes/id id})])))}
(d/div
(d/h2 "Recipe Details")
(d/p (str "ID: " id))
(d/p (str "Name: " name))
(d/p (str "Description: " description))))
(defsc Recipe [this {:recipes/keys [id name]}]
{:ident :recipes/id
:query [:recipes/id :recipes/name]
:initial-state (fn [_] {:recipes/id 1
:recipes/name "Blueberry Jam"})}
(d/a {:href (rfe/href :recipe-details {:id id})}
(d/p (str "id: " id))
(d/p (str "name: " name))))
(def ui-recipe (comp/factory Recipe {:keyfn :recipes/id}))
(defsc Main [this {:as props
:keys [all-recipes]}]
{:ident (fn [_] [:component/id ::main])
:query [{:all-recipes (comp/get-query Recipe)}]
:initial-state (fn [_] {:all-recipes [(comp/get-initial-state Recipe)]})
:route-segment ["main"]}
(d/div "All Recipes"
(when all-recipes
(d/ol
(mapv ui-recipe all-recipes)))))
(defrouter TopRouter [this {:keys [current-state pending-path-segment]}]
{:router-targets [Main RecipeDetails]}
(case current-state
:pending (d/div "Loading...")
:failed (d/div "Loading seems to have failed. Try another route.")
(d/div "Unknown route")))
(def ui-top-router (comp/factory TopRouter))
(defsc Root [this {:root/keys [router]}]
{:query [{:root/router (comp/get-query TopRouter)}]
:initial-state {:root/router {}}}
(let [top-router-state (or (uism/get-active-state this ::TopRouter) :initial)]
(if (= :initial top-router-state)
(d/div :.loading "Loading...")
(d/div
(d/a {:href (rfe/href :main)}
#_{:onClick #(dr/change-route this ["main"])} "All Recipes")
#_(d/button {:onClick #(dr/change-route this ["recipes" 1])} "Recipe Details")
(ui-top-router router)))))
unless you installed HTML5 routing and route-history there IS NO integration with the browser
actually, what I do recommend is you use RAD’s utilities, which let you change route directly to a target, and it figures it out for you
(it makes the assumption that a leaf doesn’t have more than one possible path, which is not a constraint dr imposes)
https://cljdoc.org/d/com.fulcrologic/fulcro-rad/1.0.1/api/com.fulcrologic.rad.routing#route-to!
I guess that’s ok. The beauty of using the RAD way is that you can “jump” to a target source from the code…e.g. (rroute/route-to! this ReceipeDetails {:id id})
causes it to figure out the path for you, and also makes it so you can use your IDE nav to jump there
the other issue with using URIs and core dr is that it doesn’t refactor well. If you move the UI araound (e.g. nest what you have a level deeper) then all the paths need to change. With the RAD way it auto-heals. If you want old routes to continue to work (historic bookmarks) that is a simple startup map from old URL to new target.