Understanding Om queries and UI composition

157 views Asked by At

I've been trying to wrap my head around queries. Let's say I want a Root component with multiple table-views on it.

The official tutorial suggests - one approach would be to have a table-view component with no query. And then you can pass whatever data it needs to use via props, and that works just fine.

But that's very simple case. In non-trivial app, probably you'd like to have TableView with a query, because down the UI tree you may have some complex UI structure of components - table-header, footer, rows, cells, etc. Now this tutorial suggests approach with a query:

And yet this is still somewhat simplified example. So let's say if I have:

(defmethod read :numbers/odd [_ _ _]
  {:value (filter odd? (range 50))})

(defmethod read :numbers/even [_ _ _]
  {:value (filter even? (range 50))})

in real app of course the data would come from the back-end and Om would stick it into the state atom (as usual)

now I need to have a TableView component with a query that can render either of these (or any sequence in this case). So you see I have to somehow tell the TableView component to use data that sits somewhere else in the state atom. And the query for the TableView should be "dynamic", so I can possibly use multiple TableViews rendering different data.

Let's say we'd have something like this for the Root:

(defui Root
    (query [_] [{:table/odd ,,,} {:table/even ,,,}])
    (render
      [this]
      (let [{:keys [table/odd table/even]}]
        (html [:div
              [:div.odds (ui-table-view odd)]
              [:div.evens (ui-table-view even)]]))))

for brewity I omitted Om.Next interfaces

Now I have a few questions:

  • How should the query for Root look like?
  • Should I have parametrized query in TableView (where I would maybe indicate a key for the data in the state atom)? Or how else I can tell one TableView to use :numbers/odd and the other to use :numbers/even?
  • If I use parametrized query in TableView then how do I pass params from Root to TableView?
  • Maybe I should pass data or link to TableView's data via computed props?
  • How would I use then om/get-query (if sub-query is parametrized)?
  • How would read methods look like? Do I need to "move things around" in the atom at read? Doesn't sound like a good idea

Can someone please show me an example of some sort. Thanks a lot!.

1

There are 1 answers

0
iLemming On BEST ANSWER

So this is what I come up with:

For each table store a data key associated with it and then in the read phase grab that data and assoc to the map representing the table:

So if we have couple of tables (with odd and even numbers):

    {:app/tables
    [{:id       0
      :title    "Odd numbers"
      :data-key :data/odds}
      {:id       1
      :title    "Even numbers"
      :data-key :data/evens}]}

This what would read method look like:

    (defmethod parsing/read :app/tables
      [{:keys [state parser] :as env} k _]
      (let [ts                (get @state k)
            merge-table-data' (fn [{:keys [data-key] :as t}]
                                (assoc t :table/data
                                      (->> data-key
                                           vector
                                           (parser env)
                                           vals
                                           flatten)))]
        {:value (map merge-table-data' ts)}))

This approach has one big drawback - it will try to parse ALL the data for all the tables, so I need to find a way to improve it - I want to be able to selectively specify tables to grab data for.

snippet for the entire solution is here:

upd: I made an improved version (added a file to the gist). In that example now you can specify the data keys, so it would load only specified pieces

upd2: Apparently this approach somehow breakes mutations. The idea is right though - need to make use of Om.Next's normalization mechanics. I'll try to update the gist sometime later.