Skip to content

Instantly share code, notes, and snippets.

@martinklepsch
Created November 15, 2021 14:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martinklepsch/c090f114a0b9d95b0b4ae5809ef22f3b to your computer and use it in GitHub Desktop.
Save martinklepsch/c090f114a0b9d95b0b4ae5809ef22f3b to your computer and use it in GitHub Desktop.
Semi-idiomatic wrapper around XState for ClojureScript
(ns icebreaker.xstate
"Convenience methods for working with XState in a ClojureScript app.
This namespace does not attempt to fully cover every feature of XState.
Automatically enables the XState inspector when ?xstate-debug URL param is present"
(:refer-clojure :exclude [send])
(:require ["@xstate/inspect" :as xstate-inspect]
["@xstate/react" :as xstate-react]
["xstate" :as xstate]
[applied-science.js-interop :as j]
[cljs-bean.core :as cljs-bean]))
(defn inspect
"It is very important that his function is called before `.start` of the
machines you want to inspect."
[{:keys [iframe]}]
(xstate-inspect/inspect #js {:iframe iframe}))
(def debug?
(-> (js/URLSearchParams. js/window.location.search)
(.get "xstate-debug")
(boolean)))
(defonce inspector
(when debug?
(js/console.log ::open-inspector!)
(inspect {})))
(defn ->context
[initial]
#js {"__cljs_context" initial})
(def context-key
"__cljs_context")
(defn get-context
[context]
(j/get context context-key))
(defn send
[event]
(if (string? event)
(xstate/send event)
(xstate/send (clj->js event))))
(defn assign
[updater]
(xstate/assign
(fn [context event]
(j/assoc! context
context-key
(updater (get-context context) (cljs-bean/->clj event))))))
(defn guard
[guard-fn]
(fn [context event]
(guard-fn (get-context context) (cljs-bean/->clj event))))
(defn add-guards!
[js-obj guards-map]
(doseq [[k guard-fn] guards-map]
(j/assoc-in! js-obj [:guards k] (guard guard-fn)))
js-obj)
(defn add-actions!
[js-obj actions-map]
(doseq [[k action-fn] actions-map]
(j/assoc-in! js-obj [:actions k] (assign action-fn)))
js-obj)
(defn create-machine
[{:keys [id initial context states]} {:keys [guards actions]}]
(xstate/createMachine
#js {:id id
:initial initial
:context (->context context)
:states (clj->js states)}
(-> #js {}
(add-guards! guards)
(add-actions! actions))))
(defn interpret
[machine]
(xstate/interpret machine #js {:devTools debug?}))
(defn use-machine
"Like useMachine but instead of returning entire state of Machine, just return
context. This might be something to revisit at some point but this way we can
ensure that the thing we get back is the ClojureScript datastructure we're
storing in context.
https://xstate.js.org/docs/packages/xstate-react/"
[machine opts]
(let [actor (xstate-react/useMachine machine (clj->js opts))
state (first actor)]
[(get-context (j/get state :context)) (second actor)]))
(defn use-actor
"Like useActor but instead of returning entire state of Actor, just return
context. This might be something to revisit at some point but this way we can
ensure that the thing we get back is the ClojureScript datastructure we're
storing in context.
https://xstate.js.org/docs/packages/xstate-react/"
[service]
(let [actor (xstate-react/useActor service)
state (first actor)]
[(get-context (j/get state :context)) (second actor)]))
(defn use-selector
"Like useSelector but applies selector function to CLJS context.
(-> (js/React.useContext popover/*popover-seq-machine*)
(xstate/use-selector :showing))"
[service selector]
(let [select (fn [state]
(selector (get-context (j/get state :context))))]
(xstate-react/useSelector service select =)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment