Created
November 15, 2021 14:44
-
-
Save martinklepsch/c090f114a0b9d95b0b4ae5809ef22f3b to your computer and use it in GitHub Desktop.
Semi-idiomatic wrapper around XState for ClojureScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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