Fork me on GitHub
#fulcro
<
2020-08-06
>
piobaire00:08:42

Hey all, I cloned the Fulcro RAD demo on Friday, and tinkered a little with it, and it was running fine. This afternoon, when I started it back up, I'm seeing several errors in my console when it tries to load.

main.js:2232 ReferenceError: fulcro_network_csrf_token is not defined
    at eval (application.cljc:89)
    at $fulcrologic$rad$application$fulcro_rad_app [as fulcro_rad_app] (application.cljc:94)
    at eval (client.cljs:31)
    at eval (<anonymous>)
    at Object.goog.globalEval (main.js:836)
    at Object.env.evalLoad (main.js:2229)
    at main.js:3255
And
TypeError: com.example.client.init is not a function
    at eval (shadow.module.main.append.js:4)
    at eval (<anonymous>)
    at Object.goog.globalEval (main.js:836)
    at Object.env.evalLoad (main.js:2229)
    at main.js:3256
And
Uncaught TypeError: com.example.client.init is not a function
    at (index):12

piobaire00:08:34

I reverted all my changes after I saw this, thinking I had made a mistake, and then restarted the whole thing, and I'm seeing the same errors.

Tyler Nisonoff00:08:22

I think I got this when accidentally hitting the wrong port

Tyler Nisonoff00:08:40

Are you going to localhost:3000?

piobaire00:08:37

Well, it was running on 3000 last week, but I noticed this when starting it today:

$ npx shadow-cljs watch main
shadow-cljs - config: /Users/diarmad/work/personal/scheduleApp/fulcro-rad-demo/shadow-cljs.edn
shadow-cljs - starting via "clojure"
shadow-cljs - HTTP server available at 
shadow-cljs - server version: 2.8.110 running at 

piobaire00:08:44

So I was using 8081.

piobaire00:08:55

And in fact, on 3000, nothing loads at all.

piobaire00:08:04

(Which I expected after seeing the above message)

Tyler Nisonoff00:08:13

Are you running the server as well?

piobaire00:08:58

Ahhhh, hmmm... I think I stopped my server on accident actually.

Tyler Nisonoff00:08:59

You want to use the port the server is using - should be in the early logs I asked about the 8081 a week or two ago and was told to ignore that — it’s a server shadow-cljs spins up so you can interact with the front end without a backend

piobaire00:08:06

I just looked at my terminal window, and it's stopped.

piobaire00:08:20

Thanks for the reply Tyler!

Tyler Nisonoff00:08:54

For sure! I ran into the same issue not too long ago 🙂

piobaire00:08:18

Yes, that's exactly what happened. Not sure why the server died. I didn't kill it - I hadn't even tabbed into my terminal all day.

piobaire00:08:38

Awesome, thanks for the help! I'm super green with clojure, and it's still a lot to learn haha

tony.kay07:08:10

RAD update. It’s been a while since I’ve mentioned RAD, and I have been using it quite a bit in my own projects. It’s still marked alpha, though most of it is much better than that at this point…I just don’t have time to polish it. My personal opinion is that RAD should be considered in any Fulcro project. I find the add-ons it provides and modeling approach quite helpful in eliminating boilerplate and reducing errors (e.g. I have not written a back-end save mutation in months…I just send a normalized diff (standard fulcro/form-state fare) to a centralized mutation that handles it). The routing bits of RAD are usable in ANY Fulcro project, and just require a little bit more care and setup, but give some really nice benefits. RAD’s general philosophy is one of “adopt what you want, and escape any time”. There is nothing in RAD that will tie you down or add complexity that you have to untangle yourself from. Use a db adapter or use raw pathom. Use a UI plugin or hand-render everything. Use only Fulcro’s defsc for UI or leverage RAD’s helper macros for forms and report. It is all ala carte. I don’t have a lot of time lately, but I threw together two hours of videos that I’ll upload to YouTube that give you a basic tour. I’ve also published the source (it’s a simplified RAD demo repo that uses Datomic only). Hopefully the videos will inspire you to start playing with it. I find it absolutely critical in my day-to-day development at this point, and I use it on the web and in native (there is no native rendering plugin yet, but it is still super handy). I apologize that the videos are rather long and not that great. Very short on time, but wanted to get something out there for people to look at. This one starts out server-centric with some exploration of the demo at the end: https://youtu.be/H2XY5kdcjGU This one more shows you what it is like to build something from scratch: https://youtu.be/P2up8qcDmJs

tony.kay07:08:02

@souenzzo hopefully those give you some direction on your “best practices” project. I really think designing your model with the approach of RAD (independent of db) and using component options goes a long way towards describing how I use Fulcro in production independent of how much of RAD’s implementation you leverage. The general structure just works really well for data-driven apps. That second video, in particular, highlights how the open maps of attributes and components can be leveraged to accomplish a lot of automatic stuff without having to hand code it over and over, and hopefully I explain well enough how the attribute definitions and normalized form state diffs make it super easy to integrate with any db. I haven’t written any more db adapters, but I’d love to see a client-side Firebase adapter for RAD. It would be pretty cool to be able to stand up some forms and reports around a network-based DBAAS. If you delve into the internals of things like picker options (which uses floating roots) you’ll also see useful newer things that aren’t stressed in the book much, like joining a dropdown of normalized things into the UI without having to compose it into the query.

👍 30
metal 6
❤️ 6
tvaughan19:08:59

I'm having a heck of a time understanding how to get different components to react to changes in shared data. Let's say I have a list of teams in a list, when a team is clicked the team should be highlighted in the list and some additional info about it should be displayed elsewhere. Bellow is a stripped down version these components. I've tried numerous approaches, unfortunately none of them have worked completely. In the example below, when a team is clicked I do see the correct request and response between the client and server, and the team and selected-team are updated in Fulcro's client-side DB. However, despite the network traffic and state updates, the UI does't update. I'm sure I'm missing something basic. I've re-read the book, watched some YouTube videos I thought were relevant, and looked at the template repo and I still can't figure out what exactly it is I need to do to make this work. Any help is greatly appreciated. To be clear, I would like these two components to react to changes to :ui/selected-team. Thanks.

(defsc TeamName
  [this {:keys [team/id team/name team/created-at ui/selected-team] :as props}]
  {:query [:team/id :team/name :team/created-at :ui/selected-team]
   :ident :team/id}
  (dom/div :.item
           {:classes [(when (= [:team/id id] selected-team) "active")]
            :onClick #(load! this [:team/id id] TeamCard {:target [:ui/selected-team]})}
           (dom/div :.content
                    (dom/div :.header name)
                    (dom/div :.description description))))

(defsc TeamChooser
  [this {:keys [team-chooser/team-names ui/selected-team] :as props}]
  {:query [{:team-chooser/team-names (get-query TeamName)} :ui/selected-team]
   :ident (fn [] [:component/id :team-chooser])}
  (dom/div :.ui.two.column.relaxed.grid
           (dom/div :.column
                    (dom/h1 :.dividing.header
                            "Teams")
                    (dom/div :.ui.two.column.grid
                             (dom/div :.column
                                      (dom/button :.fluid.ui.labeled.positive.basic.button
                                                  {:onClick #(transact!! this [(mutations/create-team!)])}
                                                  "Create New Team")))
                    (dom/div :.ui.divided.relaxed.selection.list
                             (map ui-team-name (sort-by :team/created-at > team-names))))
           (dom/div :.column
                    (when selected-team
                      (dom/div :.stuck-top
                               (ui-team-card selected-team))))))

JAtkins20:08:29

The reason it fails is cuz they are not both pointing at the same :ui/selected-item

JAtkins21:08:45

;; Your db

{:team/id {tid {:team/id          tid
                :team/name        "..."
                :team/created-at  ...
                :ui/selected-team nil}
           ...}
 :component/id {:team-chooser {:team-chooser/team-names [[:team/id tid] ...]
                               :ui/selected-item        tid}}}

JAtkins21:08:24

The TeamName will get:

{:team/id          tid
 :team/name        "..."
 :team/created-at  ...
 :ui/selected-team nil}
and TeamChooser will get:
{:team-chooser/team-names [[:team/id tid] ...]
 :ui/selected-item        tid}

JAtkins21:08:30

The best thing to do here is to have a mutation and computed props

tvaughan21:08:10

OK. Cool. I do see the team card when I change the target in the onClick handler to point to :component/id :team-chooser :ui/selected-team , however the team name still isn't being marked as active. As you point out that's nil.

tvaughan21:08:29

> The best thing to do here is to have a mutation and computed props Any documentation or examples you recommend I look at?

JAtkins21:08:35

Is there a 3rd component?

JAtkins21:08:53

I'm looking at the code and I feel like something is missing.

tvaughan21:08:42

Yes, ui-team-card . That could easily be in-lined, especially for this example

JAtkins21:08:09

What is the use of the load? What are you expecting it to do?

tvaughan21:08:49

It makes an API call to fetch additional data about the team. I do see this working as expected in the Fulcro inspector

JAtkins21:08:21

Ok, thought you were using it for something else

JAtkins21:08:46

So, when you click a TeamName, it should become selected and update TeamChooser .. ui/selected-item?

tvaughan21:08:14

Correct. With the change you mentioned above, everything is working as expected except for "active" being toggled in TeamName

tvaughan21:08:08

Which is why I had the :target set the way I did. I thought they'd both point to the same :ui/selected-team at the root of the DB

JAtkins21:08:03

This should be approximately correct then

JAtkins21:08:04

(defmutation set-team-selection [{tid :team/id}]
  (action [{:keys [state app]}]
    (swap! state assoc-in [:component/id :team-chooser :ui/selected-team] tid)
    (load! app ...)))

(defsc TeamName
  [this
   {:as props :keys [team/id team/name team/created-at]}
   {:as computed-props :keys [ui/draw-selected?]}]
  {:query [:team/id :team/name :team/created-at]
   :ident :team/id}
  (dom/div :.item
    {:classes [(when draw-selected? "active")]
     :onClick (fn [x] 
                (comp/transact! this [(set-team-selection props)]))}
    (dom/div name description)))

(defsc TeamChooser
  [this {:keys [team-chooser/team-names ui/selected-team] :as props}]
  {:query [{:team-chooser/team-names (get-query TeamName)} :ui/selected-team]
   :ident (fn [] [:component/id :team-chooser])}
  (dom/div 
    (dom/div 
      (dom/h1 
        "Teams")
      (dom/div 
        (dom/div 
          (dom/button 
            {:onClick #(transact!! this [(mutations/create-team!)])}
            "Create New Team")))
      (dom/div 
        (map (fn [team]
               (ui-team-name (comp/computed team 
                               {:ui/draw-selected? (= (:team/id team) selected-team)})))
          (sort-by :team/created-at > team-names))))
    (dom/div 
      (when selected-team
        (dom/div 
          (ui-team-card selected-team))))))

JAtkins21:08:22

The reason to use computed is simple - fulcro keeps track of each TeamName instance and id that is on the screen. Sometimes if that data is changed directly, fulcro will update the component and not TeamChooser, thus avoiding the nice logic you put in to tag it as :ui/selected?. If you use computed, fulcro will keep track of that and add it back to your props even if TeamChooser is not rendered

JAtkins21:08:38

This will save you tons of headaches if used correctly

tvaughan22:08:49

Ah ok. It had not occurred to me to use computed props for this. Thanks for the suggestion and the awesome example. I really appreciate this, @U5P29DSUS! This is super helpful! I'll report back on my success tomorrow :hand_with_index_and_middle_fingers_crossed:

JAtkins22:08:38

NP, glad I could help

tvaughan12:08:15

I played around with it a bit and this works:

(defsc TeamName
  [this {:keys [team/id team/name team/description team/created-at] :as props} {:keys [selected?]}]
  {:query [:team/id :team/name :team/description :team/created-at]
   :ident :team/id}
  (dom/div :.item
           {:classes [(when selected? "active")]
            :onClick #(load! this [:team/id id] TeamCard {:target [:component/id :team-chooser :selected-team]})}
           (dom/div :.content
                    (dom/div :.header
                             name)
                    (dom/div :.description
                             description))))

(defsc TeamChooser
  [this {:keys [team-chooser/team-names selected-team] :as props}]
  {:query [{:team-chooser/team-names (get-query TeamName)} {:selected-team (get-query TeamCard)}]
   :ident (fn [] [:component/id :team-chooser])
   :route-segment ["teams"]
   :initial-state {}}
  (dom/div :.ui.two.column.relaxed.grid
           (dom/div :.column
                    (dom/h1 :.dividing.header
                            "Teams")
                    (dom/div :.ui.two.column.grid
                             (dom/div :.column
                                      (dom/button :.fluid.ui.labeled.positive.basic.button
                                                  {:onClick #(transact!! this [(mutations/create-team!)])}
                                                  "Create New Team")))
                    (dom/div :.ui.divided.relaxed.selection.chooser
                             (let [selected-team-id (:team/id selected-team)
                                   sorted-team-names (sort-by :team/created-at > team-names)]
                               (map #(ui-team-name (computed % {:selected? (= (:team/id %) selected-team-id)})) sorted-team-names))))
           (dom/div :.column
                    (when selected-team
                      (dom/div :.stuck-top
                               (ui-team-card selected-team))))))
w00t!

dvingo20:08:16

You may just need to join the TeamCard in your queries, as the load result will be normalized and an ident will be stored at :ui/selected-team:

{:ui/selected-team (c/get-query TeamCard)}

tvaughan20:08:24

Thanks for the suggestion. Unfortunately, same behavior