Fork me on GitHub

Now that I have a connection to the GraphQL API up and running, I’m trying to figure out how to write a load! for the SearchResults component below.

(defsc Article
  [this {:zd.Article/keys [article_doi article_title]}]
  {:ident :zd.Article/article_doi
   :query [:zd.Article/article_doi
  (div article_title))
(defsc SearchResults
  [this {:search-results/keys [id articles]}]
  {:ident :search-results/id
   :query [:search-results/id
		   {:search-results/articles (get-query Article)}]}
	(map article articles)))
Given that search queries look like this,
   {:n_results 1
	:filter_expr {:should [{:must [{:terminal_exprs {:matches_text "Water"}}]}]}})
And returns stuff like this:
 [{:zd.Article/article_title "Drinking Water",
   :zd.Article/article_doi "10.1177/1084822313481784"}]}
I suppose I want to write a resolver that is something like this (the only data of interest to the search are params, so I’m guessing it becomes global),
{::pc/output [{:search-results/articles […]}]}
` With being some mysterious join into the world of the GraphQL API, and specifically the search_articles_advanced_2 entry point above (with the filter_expr being supplied as a param, I guess) so that I can ultimately do
(df/load! this [:search-results/id some-id] SearchResults
  {:target [:search-results/id some-id :articles]
   :filter-expr {:should {:must ["Water"]}}}
I’m not sure how to supply this glue—how would one do it?


@henrik hello, one question, who is performing the actual search, the graphql API or something you are writing on pathom?


Hey, the GraphQL endpoint is performing the search.


It can basically be considered external, like your Youtube and SpaceX examples from the video.


because, in this case, you may want to do that directly, make the components queries in a way that already aligns with them


one thing that's not obvious is how to set the parameters, IME I found that writing a fn that returns the query with the params is most straitforward way to do it, something like:


(defn search-query [text-match]
     {:n_results   1
      :filter_expr {:should [{:must [{:terminal_exprs {:matches_text text-match}}]}]}})


if you really want to rename the result somehow (instead of using the GraphQL names directly on your components), you can trigger a sub-query in a resolver, let me get you an example

👍 4

I’m not sure how to write the components to line up with the GraphQL API. Is,

   {:n_results 1
    :filter_expr {:should [{:must [{:terminal_exprs {:matches_text "Water"}}]}]}})
even an acceptable query for a component in Fulcro, especially given that n_results and filter_expr are dynamic?


the params part you need to fill during hte load! trigger (so you use the search-query fn as described before)


this is the example to run the sub-query internally:


(pc/defresolver search-articles [env _]
  {::pc/output [{:search-results/articles 
   ; this ::pc/params is more a documentation thing, doens't affect any runtime properties
   ::pc/params [:my-system/search-query]}
  (let [search-text (-> env :ast :params :my-system/search-query)
        ; if you are using async or parallel parsers, p/entity will return a core.async channel
        ; and you must wait for it
        search-result (p/entity env [{(:zd/search_articles_advanced_2
                                        {:n_results   1
                                         :filter_expr {:should [{:must [{:terminal_exprs {:matches_text search-text}}]}]}})
    ; write code to re-format search-results in whatever you want


to change the query, on the load! trigger you can use the property :update-query, this way you can replace the query entirely (there is where you would call search-query)


I’m slowly wrapping my head around it. The sub-query solution seems to assume that

Are hard-coded, whereas I would very much like them to flow from the fact that they were selected by the Fulcro component.


yeah, considering that, I would suggest you just use the final GraphQL names on the component, I can do a quick write on how I imagine it, once sec 🙂

🙏 4

what about this?


(defsc Article
  [this {:zd.Article/keys [article_doi article_title]}]
  {:ident :zd.Article/article_doi
   :query [:zd.Article/article_doi
  (div article_title))

(defn load-search-results [component query-text]
  (df/load-field component :zd/search_articles_advanced_2
    {:params {:n_results   1
              :filter_expr {:should [{:must [{:terminal_exprs {:matches_text query-text}}]}]}}}))

(defsc SearchResults
  [this {:search-results/keys [id articles]}]
  {:ident :search-results/id
   :query [:search-results/id
           {:zd/search_articles_advanced_2 (get-query Article)}]}
    (dom/button {:onClick #(load-search-results this "search")} "Search!")
    (mapv article articles)))


Processing… Please wait.


Seems far better by the look of it.


Regarding load-field, the search results component won’t own the search function. It must come from a parent of both the results component and the search field/input component. How would you deal with this? Extract it from the component registry?


Other than that conundrum, I like this solution. The component query very explicitly shows directly how it depends on the search API, which is nice. The less impedance mismatch, the better.


I guess that a solution to the above is to just store the results in the top-level component rather than in the sub-component of SearchResults.


@henrik didn't get the load-field part, the data will live inside of the SearchResults ident in this case, would be good if you have some id for the search results component, that can be a random generated uuid


its ok for pathom to do that query from inside of an ident, pathom should generate the appropriated GraphQL query in this case, even so it not top level


there is another way to trigger that load, maybe this:


(defn load-search-results [component query-text]
  (df/load component :zd/search_articles_advanced_2
    {:target (conj (fp/get-ident component) :zd/search_articles_advanced_2)
     :params {:n_results   1
              :filter_expr {:should [{:must [{:terminal_exprs {:matches_text query-text}}]}]}}}))


(I'm not testing any of this, so some details may need adjust)


So, looking at the query that load! sends to the Pathom parser, it ends up being:

[({[:search-results/id :an-id]
     [:zd.Article/article_doi :zd.Article/article_title]}]}
  {:n_results 1,
   {:should [{:must [{:terminal_exprs {:matches_text "hello"}}]}]}})]


(with some dummy data, just to see what it says)


Sorry, I may have done something stupid.


Ah, yes, here’s the missing ticket:

(df/load app :zd/search_articles_advanced_2 Article …)
Give it Article, or otherwise it doesn’t select any fields in the query to GraphQL.


Well, that certainly goes out and loads some data! Awesome!


(Since I haven’t actually wired the components into the UI yet, I wanted to try it out on app before assuming that I’ve understood more than I have)


I’m going to have to buy you a beer next time I’m in Brazil.

😄 4
🍻 4
🍺 4

You think I’m joking, but my wife is a Paulista 😁


nice! I'll be glad to have a beer with you! do you have plans to visit here soon?


We were going to go at the end of the year, but a surprise happened and all her family is coming to Stockholm instead! So preliminarily, early next year.


Silly question, is there any way to rename a resolver with a single input defined with defresolver or is it always auto-named? If so, what's the best practice? I of course can rename the input which changes the resolver name, but it's a bit odd destructuring a param that's really just there to change the name. Maybe using aliases/alias resolvers is possible?


@twicebaked is worth noticing that those helpers just return maps, so the easiest way to rename is doing: (assoc (pc/single-attr-resolver ...) ::pc/sym 'my/new-name)


OK, yeah I figured that manipulating pc/sym waas what would do it. Just wondered if there was a simpler way maybe via the macro itself but didn't see anything obvious in the source right away. Thanks.


no built-in helper, but you can make your own 🙂


this project is serious about keeping things as stable as possible (include keywords), so you can count that ::pc/sym is not something that will change