Fork me on GitHub
Alister Lee08:12:11

@tony.kay what do you think about a change to wrap-api and handle-api-request to cause selected keys from the request (eg. session) to be passed through to the parser environment? (unfortunately, would change the signature of the parser fn...)

Alister Lee08:12:51

My intent is to allow resolvers to know the authenticated user from the session.

Robin Jakobsson11:12:17

What is the correct mental model for what an ident is? Should every component have it’s own ident?


It is like a row on a table in a database: [table-name table-pk].


Some components right at the end of the tree (leaf components I guess) don't need to have idents. Also the root component doesn't have an ident. All the others - try to put idents on them. Even the singleton components (think panels on the UI) have idents.

👍 8

The choice of ident/no ident should not have anything to do with the position in the tree, but rather if the component itself has data you’d like to manage in a normalized manner. If the component exists purely as a shouldComponentUpdate optimization and has no local data, then it definitely does not need an ident. Root is special, and (currently) cannot have an ident because it overlays the root of the db.


But as @U0D5RN0S1 says: most components end up having idents even if they are singletons just so you know where their data is in the db.


I just happen to have stateless 'controls' at the end of the tree. For instance I have a navigation control that doesn't need an ident, just gets given some props it displays and some computed functions. I just thought that if you implemented your own buttons, labels, inputs etc - they would be controls that also did not need idents.


ah, I see what you meant

Robin Jakobsson13:12:12

When is it right for a component to share an ident with another component?


When you have a different UI view of the same row of data. For example you may have less attributes on some kind of view that is used in a UI list, than a detail view.

👍 12

@clojure388 Yeah, this was a bit of oversight in my move to F3. I thought it would be cleaner to have it be a separate concern, so you end up writing your own wrap-api as a result, like this:


It’s quite common to want what you want, it’s just very very common for you to want more than that…so you probably just end up needing to write you own wrap-api in general.


I made a change in the websockets support to add the option parser-accepts-env?, and would be glad to add this (or an alternate function) to the middleware ns. It really wasn’t the best change on my part from F2 😕


Hi I am finding Fulcro to be just what I need for my project! Very excited to be working with it! I have a question: I am trying to add a new name/age to the example project in the book. I am having problems working with tempids. Can anyone point me in the correct direction, or provide a code fragement? I can post my code if desired.


are you using fulcro3?


Here's my code:


(ns app.ui
    [app.mutations :as api]
    [com.fulcrologic.fulcro.components :as comp :refer [defsc]]
    [com.fulcrologic.fulcro.dom :as dom]
    [com.fulcrologic.semantic-ui.collections.grid.ui-grid :refer [ui-grid]]
    [com.fulcrologic.semantic-ui.collections.grid.ui-grid-column :refer [ui-grid-column]]
    [com.fulcrologic.semantic-ui.collections.grid.ui-grid-row :refer [ui-grid-row]]
    [com.fulcrologic.semantic-ui.elements.button.ui-button :refer [ui-button]]
    [com.fulcrologic.semantic-ui.collections.table.ui-table :refer [ui-table]]
    [com.fulcrologic.semantic-ui.collections.table.ui-table-header :refer [ui-table-header]]
    [com.fulcrologic.semantic-ui.collections.table.ui-table-header-cell :refer [ui-table-header-cell]]
    [com.fulcrologic.semantic-ui.collections.table.ui-table-body :refer [ui-table-body]]
    [com.fulcrologic.semantic-ui.collections.table.ui-table-row :refer [ui-table-row]]
    [com.fulcrologic.semantic-ui.collections.table.ui-table-cell :refer [ui-table-cell]]

(defsc Person [this {:person/keys [id name age] :as props} {:keys [onDelete]}]
  {:query [:person/id :person/name :person/age]
   :ident (fn [] [:person/id id])}
  (ui-table-row nil
                (ui-table-cell nil name)
                (ui-table-cell nil age)
                (ui-table-cell nil
                               (ui-button {:onClick #(onDelete id)} "X"))))

(def ui-person (comp/factory Person {:keyfn :person/id}))

(defsc AddPerson [this props {:keys [tempid]}]
  (let [add-person (fn [] (comp/transact! this [(api/add-person
                                                  {:tempid      tempid
                                                   :list/id     :friends
                                                   :person/name "Mom"
                                                   :person/age  76})]))]
    (ui-button {:onClick #(add-person)} "Add Mom!")))

(def ui-add-person (comp/factory AddPerson))

(defsc PersonList [this {:list/keys [id label people] :as props}]
  {:query [:list/id :list/label {:list/people (comp/get-query Person)}]
   :ident (fn [] [:list/id id])}
  (let [delete-person (fn [person-id] (comp/transact! this [(api/delete-person
                                                              {:list/id   id
                                                               :person/id person-id})]))]
    (map (fn [p] (ui-person (comp/computed p {:onDelete delete-person}))) people)))

(def ui-person-list (comp/factory PersonList))

; Root's initial state becomes the entire app's initial state!
(defsc Root [this {:keys [friends enemies] :as props}]
  {:query         [{:friends (comp/get-query PersonList)}
                   {:enemies (comp/get-query PersonList)}]
   :initial-state {}}
  (dom/header {})
  (dom/div {}
           (ui-grid nil
                    (ui-grid-row {:columns 2 :divided true}
                                 (ui-grid-column nil
                                                 (when friends
                                                   (ui-table nil
                                                             (ui-table-header {:fullWidth true}
                                                                              (ui-table-row nil
                                                                                            (ui-table-header-cell nil "Friends")))
                                                             (ui-table-body nil
                                                                            (ui-person-list friends)))))
                                 (ui-grid-column nil
                                                 (when enemies
                                                   (ui-table nil
                                                             (ui-table-header {:fullWidth true}
                                                                              (ui-table-row nil
                                                                                            (ui-table-header-cell nil "Enemies")))
                                                             (ui-table-body nil
                                                                            (ui-person-list enemies))))))
                    (ui-grid-row {:columns 1} (ui-add-person (comp/computed props {:tempid (fulcro.tempid/tempid)}))))))


I am still really new to this, so I'm not sure what is wrong...


i think i made this way too complicated. Let me try again.


@hadilsabbagh18 So you need to 1) get the new person data merged into the client db and 2) swap out the tempid with the real ID generated on the server. For the first step, the easiest way is using merge-component. In your local “addPerson” mutation, create a map with your person data (name, tempid, etc.) and then pass this to merge-component. This is basically the optimistic side of the update. For the second part, in your pathom mutation on the server, you add the new data to the server db and generate the “real” ID. Then in the map that is returned from the pathom mutation, add a :tempids key pointing to a map of tempid->realid. Fulcro will then take care of swapping the ids in the client database. There is an easier way for your simple example. In this case, since you are just adding static data (not letting the user edit it before creating the data) you could use a mutation join and skip the tempid. But the steps I outlined are more practical for how you would normally add data. Hope this helps.


Thanks @mdhaney! I appreciate your time in answering my question!


No problem! It’s a lot to wrap your head around at first, but so worth the effort IMO.