A ClojureScript microlib for state management in a Helix-based React app
deepstate provides some simple hooks-based state management primitives which probably aren't very performant as the single source of truth in a large app (there are no reactions), but are simple, flexible, and straightforward to use in an async world.
(require '[deepstate.action :as a])
(require '[deepstate.action.async :as a.async])
deepstate reduces a stream of events onto a state value.
There are a few core concepts:
action-context
- a React context for some deepstate stateaction-context-provider
- an element providing an action-context
to
a component treeuse-action
- a hook used by components to interact with state. It
returns a state
value and a dispatch
functiondispatch
- a fn returned by the use-action
hook which sends an
action to be handleddef-action
- a macro which defines a generic action handler. There
are more specialised variants such as
def-state-action
- defines an action handler which only modifies stat:edef-async-action
- defines an action handler which runs a promise-based
async computation and records the status and result in a standard schemadef-axios-action
- an async action which parses axios results:Perhaps the simplest example:
(a/def-state-action ::inc-counter
[state action]
(update state ::counter inc))
(def action-ctx (a/create-action-context))
(defnc App
[]
(let [[counter dispatch] (a/use-action action-ctx [::counter])]
(d/div
(d/p "Counter: " counter)
(d/button {:on-click (fn [_] (dispatch ::inc-counter))} "inc"))))
A more complex example, demonstrating the common schema for promise-based async actions:
(a/def-axios-action ::fetch-apod
[state action]
{::a/axios (axios/get "https://api.nasa.gov/planetary/apod\?api_key\=DEMO_KEY")})
(def action-ctx (a/create-action-context))
(defnc App
[]
(let [[{status ::a/status
result ::a/result
:as apod}
dispatch] (a/use-action action-ctx [::fetch-apod])]
(d/div
(d/p "Status:" (str status))
(d/p "APOD:" (pr-str result))
(d/button {:on-click (fn [_] (dispatch ::fetch-apod))} "Fetch!"))))
An action-context
is created with a/create-action-context
, and
passed through a component tree by an a/action-context-provider
element.
Components consume state through the a/use-action
hook, which
returns [state dispatch]
- a state value and a dispatch fn.
Actions are maps which describe an operation to mutate state. They have
an ::a/action
key which selects a handler, and any other keys
the particular handler requires.
An action is dispatch
ed causing a handler to be invoked according
to the ::a/action
key in the action. The handler
returns a function of state
, which when invoked returns some
optional effects (returning no effects is not very useful, but
perfectly fine):
::a/state
- a new state value::a/navigate
- a url to navigate to::a/dispatch
- an ActionMap
| [ActionMap]
to be dispatched::a/later
- a promise of an effect fn (fn [state] ...)
Promise<Effects>
- an ::a/update-later
promise of Effects
It is possible to define an action handler directly, with
(defmethod a/handle <key> [action] (fn [state] ...))
, but it's
much easier to use one of the sugar macros...
Defines a action handler. It takes
::a/action
key used in an action map(a/def-action ::change-query
[state
{q :q
:as _action}]
{::a/state (assoc state ::query q)
::a/navigate "/show-state"})
(a/def-state-action ::change-query
[state
{q :q
:as _action}]
(assoc state ::query q))
Defines a promise-based async action handler. The action is specified as a form returning a promise of the result. The global state and the state of the particular action is available for destructuring.
(a.async/def-async-action ::run-query
[state
{{id :id} ::a/result
:as action-state}
{q :q
:as action_}]
{::a/async (run-query action)
::a/navigate-success (str "/item/" id)})
This def-async-action
will result in a map in the state at path [::run-query]
with the shape
{::a/status ::a/inflight|::a/success|::a/error
::a/result ...
::a/error ...}
Copyright © 2023 mccraigmccraig of the clan mccraig
Distributed under the MIT License.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close