Attempt at a very simple shadow-grove app

Hi everyone,

I bet there is just a really simple thing wrong here, but I’d appreciate your help.

In trying to create a very minimal app, I am running into trouble. I get this in the console:

Assert failed: (or (nil? ident) (db/ident? ident)).

Here is my full code:

(ns test1.core
  (:require                               
    [shadow.grove :as sg :refer (<< defc)]
    [shadow.grove.db :as db]))

(def schema {:note {:type :entity   
                    :primary-key :id   
                    :attrs {}   
                    :joins {}}})

(def initial-items [{:id 1 :text "This is item one."}
                    {:id 2 :text "This is item two."}])
                       
(def *data (-> {:items initial-items}
               (db/configure schema)                 
               (atom)))                                

(def *runtime (-> {}                    
                  (sg/prepare *data :db)))

(defc item [ident] 
  (bind  
    {:keys [text]}
    (sg/query-ident ident))
  (render  
    (<< [:li text])))           

(defc app []  
  (bind 
    {:keys [items]}
    (sg/query-root [:items]))
  (render
    (println 'items items)
    (<< [:h1 "Items:"]         
        [:ul (sg/simple-seq items item)]))) 

(defn render []                                  
  (let [root-el (js/document.getElementById "app")] 
    (sg/render *runtime root-el (app))))

(defn initialize! []          
  (render))

(defn ^:dev/after-load reload! []          
  (render))

You’re using integer IDs. Judging by the code and the README, shadow-grove expects an instance of a special type there. Try replacing e.g. 1 with something like (db/make-ident :item 1).

1 Like

Inspect *data and you will see the problem: db/configure does not normalize your data in any way. The following should work:

(def *data (-> {}
               (db/configure schema)
               (db/merge-seq :note initial-items [:items])
               (atom)))
1 Like

The :initial-items you added are not normalized, unless specifically added via normalizing helper functions. Currently, this can only be done as part of an event, since a helper function for direct imports is otherwise missing.

So, during initialize you trigger a event, then you import the data in that event handler. Much like you would in re-frame with the usual (rf/dispatch-sync [::init-db]) or so.

(defn initialize! []          
  (sg/run-tx! *runtime {:e ::init!})
  (render))

and to handle that

(sg/reg-event *runtime ::init!
  (fn [env ev]
    (update env :db db/merge-seq
      :post
      [{:id 1 :text "This is item one."}
       {:id 2 :text "This is item two."}]
      [:items])

db/merge-seq takes a coll of :post and stores it under :initial-items in the db.

The rest should then work.

1 Like

Currently all modifications that require normalization must be done in event handlers. Doing it outside will only modify the raw db map, but won’t record other necessary data. So, for example db/all-of would miss these.

It is also just safer to ensure everything is properly handled and notified. At some point I’ll add a helper to do things directly, but until then only modify the db via events.

I’m aware, just didn’t want to complicate further :slight_smile: I seem to have removed those warnings from the docstrings PR. I’ll add them back in.

Thanks, guys! On a final note, as a dénoument, let me simply show what this one-file minimal shadow-grove app now looks like.

As the app grows, the “Environment” section will be split off into env.cljs, the “Views,” into views.cljs, and “Core” will remain in core.cljs.

(ns test1.core
  (:require                         
    [shadow.grove.db :as db]              
    [shadow.grove :as sg :refer (<< defc)]))

;; Environment                                     

(def schema {:item {:type :entity   
                    :primary-key :id}}) 

(def *data (atom (db/configure {} schema)))                                  
                                                                               
(def *runtime (sg/prepare *data :db))                
                                                       
;; Views                                             
                                                       
(defc item [ident]                                   
  (bind                                                
    {:keys [text]}       
    (sg/query-ident ident))
  (render                                                       
    (<< [:li text])))
                                                                
(defc app []                                         
  (bind                                                
    {:keys [items]}
    (sg/query-root [:items]))
  (render                  
    (<< [:h1 "Items:"]                                          
        [:ul (sg/simple-seq items item)])))
                          
;; Core                                              
                                                       
(def initial-items [{:id 1 :text "This is item one."}
                    {:id 2 :text "This is item two."}])
                                        
(sg/reg-event                  
  *runtime :initialize!                                         
  (fn [env e]                                          
    (update env :db db/merge-seq :item initial-items [:items])))
             
(defn render []                                  
  (let [root-el (js/document.getElementById "app")] 
    (sg/render *runtime root-el (app))))                        

(defn initialize! []          
  (sg/run-tx! *runtime {:e :initialize!}) 
  (render))                                

(defn ^:dev/after-load reload! []          
  (render))

It bothered me a bit that you couldn’t directly modify the *data when creating it, so I fixed it in 0.5.0. Going through the event handler is still fine but no longer necessary.

(ns test1.core
  (:require
    [shadow.grove.db :as db]
    [shadow.grove :as sg :refer (<< defc)]))

;; Environment

(def schema {:item {:type :entity
                    :primary-key :id}})

(def initial-items [{:id 1 :text "This is item one."}
                    {:id 2 :text "This is item two."}])

(def *data
  (-> (db/configure {} schema)
    (db/merge-seq :item initial-items [:items])
    (atom)))

(def *runtime (sg/prepare *data :db))

;; Views

(defc item [ident]
  (bind
    {:keys [text]}
    (sg/query-ident ident))
  (render
    (<< [:li text])))

(defc app []
  (bind
    {:keys [items]}
    (sg/query-root [:items]))
  (render
    (<< [:h1 "Items:"]
      [:ul (sg/simple-seq items item)])))

;; Core

(defn render []
  (let [root-el (js/document.getElementById "app")]
    (sg/render *runtime root-el (app))))

(defn initialize! []
  (render))

(defn ^:dev/after-load reload! []
  (render))
1 Like