Skip to content

Instantly share code, notes, and snippets.

@Deraen
Last active October 4, 2024 19:07
Show Gist options
  • Save Deraen/24a877421f3d9012d45e480bfe885156 to your computer and use it in GitHub Desktop.
Save Deraen/24a877421f3d9012d45e480bfe885156 to your computer and use it in GitHub Desktop.
Cljs and TanStack Query
(ns foo.queries
(:require [foo.query :as q])
;; I suggest having a simple JS Fetch wrapper or something.
;; Maybe one which works with route-names (from Reitit route-data?) and renders the paths
;; with given parameters.
(defn get-todo-query [id]
#js {:queryKey #js ["todo" id]
:enabled id
:queryFn (fn [_ctx] (fetch (url :api/todo {:id id})))})
(defn remove-todo-mutation [id]
#js {:queryFn (fn [_ctx] (fetch (url :api/remove-todo {:id id}) {:method :delete}))
:onSuccess (fn [_] (q/invalidate-queries {:queryKey ["todo" id])})
(ns foo.query
(:require ["@tankstack/react-query"]
[cljs-bean.core :refer [->clj ->js bean]))
;; Check https://github.com/metosin/shadow-cljs-esbuild for ESBuild
;; example because Closure-compiler can't handle TSQ
;; Idea of using ->clj select option is to keep JSON responses in TSQ store
;; as JS objects, because TSQ devtool doesn't really handle Clj datastructures
;; and it can't be extended.
;; NOTE: This doesn't work with Transit responses as those would already be Cljs data
(def query-client (query/QueryClient.
#js {:defaultOptions #js {:queries #js {:select ->clj}}}))
;; Non-recursive lazy transform to Clj
;; Cljs-bean should also correctly handle TSQ tracked properties:
;; https://tanstack.com/query/latest/docs/framework/react/guides/render-optimizations#tracked-properties
;; Meaning that only changes to the properties that are really used (accessed/destructured from the result),
;; will trigger the component to re-render.
(defn use-query
[query]
(bean (query/useQuery query)))
;; Add some other wrappers, e.g. invalidate-queries with your query-client.
(defn invalidate-queries
[partial-query]
(.invalidateQueries query-client (->js partial-query)))
(ns foo.re-frame-use
(:require [re-frame.core :as rf])
;; EXPERIMENTAL
;; Not sure how good of an idea this is
(rf/reg-sub ::test-sub
(fn [[_ id]]
;; Signal fn is only called by re-frame when the parameters/sub vector changes,
;; so this only creates the query JS object once -> :use-query and
;; the Observer is only initialized once (unless id value changes).
(rf/subscribe [:use-query (q/get-todo-query {:id id})]))
(fn [todo _]
todo))
(rf/reg-event-fx ::test-event
(rf/inject-cofx :get-query-data (fn [[_ id]]
["todo" id]))
(fn [{:keys [queries]} _]
(js/console.log (get queries ["todo" 9]))
nil))
(rf/dispatch [::test-event 9]))
(ns foo.re-frame
(:require [re-frame.core :as rf])
;; EXPERIMENTAL
;; Not sure how good of an idea this is
;; Experimental Re-frame query that can be used to initialize and access
;; TSQ queries from Re-frame subscriptions.
;; Query lifecycle is tied to the Reaction lifecycle,
;; if the subscription is being used in a component,
;; the query is active in TSQ.
;;
;; Query value is the same as what `useQuery` would take.
;; Either cljs (will be converted) or the JS value directly.
(rf/reg-sub-raw :use-query
(fn [_app-db [_ query]]
(let [;; could also store to app-db here, using queryKey as path
store (r/atom nil)
observer (query/QueryObserver. query-client (->js query))
unsub (.subscribe observer (fn [result]
;; This stores the full response object, not just the response data
(reset! store result)))]
(ratom/make-reaction
(fn []
@store)
:on-dispose
(fn []
(unsub))))))
;; Experimental cofx to get a TSQ query response into a Re-frame event handler.
;; Because this has to be synchronous and the result has to be available,
;; this doesn't run the query, just gets the data if it is already available,
;; from useQuery.
;;
;; Note: the data is available in coeffect map under
;; :queries key, using query-key as a map key.
(rf/reg-cofx :get-query-data
(fn [coeffects param]
(let [query-key (cond
(ifn? param) (param (:event coeffects))
:else param)
data (.getQueryData query-client (->js query-key))]
;; NOTE: The data is JS object, could call `bean` here, which is our
;; default select fn.
(update coeffects :queries assoc (->clj query-key) data))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment