Forms are one of the most essential things in frontend UIs. Just displaying data is quite boring. At some point you'll want to let your users modify it in some way.
Although all the mechanisms for working with forms are present in shadow-grove
currently I consider it a substantial missing piece to have no first class form support. Forms can get complex rather quickly and unreasonably complex if you factor in accessibility concerns (eg. aria-*
attributes).
There are certain aspects in the rendering mode used by shadow-grove
(as well as react
and others) where you end up in a render cycle that can be problematic.
(defc ui-form []
(bind data-ref (atom {:hello "world"}))
(bind {:keys [hello] :as data} (sg/watch data-ref))
(render
(<< [:form {:on-submit ::submit!}
[:label {:for "hello"} "Hello: "]
[:input {:id "hello" :type "text" :value hello :on-input ::input!}]
[:button {:type "submit"} "Go!"]]))
(event ::submit! [env ev e]
(js/console.log "submit" data))
(event ::input! [env ev e]
(swap! data-ref assoc :hello (.. e -target -value))))
I'm using a local atom
here, which I consider a total anti-pattern, but it is useful for keeping this example short and concise. The same problem arises when using the properly normalized db and EQL queries. The steps that occur are:
data-ref
atom is created with the initial state of {:hello "world"}
. This will only run once.bind
will watch the first binding data-ref
. It will trigger whenever the value in data-ref
is modified.:hello
value out of the data and the render
uses it to set the :value
of the input.input
event of the text input will trigger with any input made. So suppose we add a !
. It'll trigger the ::input!
event with (.. e -target -value)
being hello!
.data-ref
accordingly, which will in turn trigger the second hook to update.hello
changed we will also and up in render
again to set :value
to "hello!"
.As you may have guessed this render was entirely unnecessary. It was already at that value since it originated from there.
This can get way out of control if you do something async (eg. talk to a server) which may take some time. If the user continues typing you now may need to abort that work. At the very least you need to make sure you don't end up resetting the value back to something outdated. Just debouncing the event is not a solution.
One way for dealing with this in react
is via "controlled" and "uncontrolled" components. You could do the same in shadow-grove
but I consider this lacking in several regards.
id
and aria-*
related attributes in some declarative manner.I have not yet settled on something for this. For now everything is in the exact same place that react
has been in since release. It is definitely workable but I'd like something better integrated and less manual.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close