This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-07-04
Channels
- # announcements (10)
- # asami (6)
- # babashka (22)
- # beginners (44)
- # biff (1)
- # calva (8)
- # clj-kondo (13)
- # clojure (62)
- # clojure-art (1)
- # clojure-europe (27)
- # clojure-nl (1)
- # clojure-norway (19)
- # clojure-spec (19)
- # clojure-uk (2)
- # component (29)
- # datascript (1)
- # fulcro (9)
- # gratitude (2)
- # kaocha (6)
- # klipse (1)
- # luminus (16)
- # malli (9)
- # nbb (5)
- # off-topic (4)
- # reagent (5)
- # shadow-cljs (85)
- # spacemacs (1)
- # tools-deps (10)
- # vim (9)
- # xtdb (2)
I was using rich-text editor (slate.js https://docs.slatejs.org/walkthroughs/02-adding-event-handlers) in fulcro and want to implement a nested structure of rich text textarea using Slate react component.
I tried to follow the fulcro book to use react-interop to use Slate react components. The problem I encountered is how I can make different textarea pulling data from the same place of client database in sync.
(you can see that at the end of my video, the task 5 textarea is not updated)
I have two questions:
1. Is it reasonable to use (ui-editor {:content content :id id :this this})
like I did in my code to pass this
object around. Or there's a better way?
2. How could I fix the problem of different textareas with same data not syncing together.
The code I wrote look like this:
(ns com.example.ui
(:require
["react" :as react]
["react-dom" :as react-dom]
["slate" :refer (createEditor)]
["slate-react" :refer (Slate Editable withReact)]
[com.fulcrologic.fulcro.algorithms.react-interop :as interop]
[com.example.mutations :as mut]
[com.fulcrologic.fulcro.algorithms.merge :as merge]
[com.fulcrologic.fulcro.algorithms.tempid :as tempid]
[com.fulcrologic.fulcro.algorithms.data-targeting :as targeting]
[com.fulcrologic.fulcro.algorithms.normalized-state :as norm]
[com.fulcrologic.fulcro.components :as comp :refer [defsc transact!]]
[com.fulcrologic.fulcro.raw.components :as rc]
[com.fulcrologic.fulcro.data-fetch :as df]
[com.fulcrologic.fulcro.mutations :refer [defmutation]]
[com.fulcrologic.fulcro.dom :as dom :refer [button div form h1 h2 h3 input label li ol p ul]]))
(declare ui-todo)
(declare ui-editor)
(defmutation change-context [{id :id data :new-data}]
(action [{:keys [state]}]
(swap! state assoc-in [:todo/id id :todo/content] data)))
(defn get-text [data props]
(let [this (goog.object/get props "this")
id (goog.object/get props "id")
children (.-children (first data))
all-data (apply str (map #(.-text %) children))]
all-data
(comp/transact! this [(change-context {:id id :new-data all-data})])))
(defn Editor [props]
(let [[editor] (react/useState #(-> (createEditor)
(withReact))) ;; bind this?
init-val (clj->js [{"type" "paragraph"
"children" [{"text" (.-content props)}]}])
editable-comp (react/createElement Editable #js {} nil)
slate-comp (react/createElement Slate
#js {"editor" editor
"value" init-val
"onChange" #(js/console.log (get-text % props))}
editable-comp)]
slate-comp))
(def ui-editor (interop/react-factory Editor))
(defsc Todo [this {:todo/keys [id content child]}]
{:query (fn [] [:todo/id :todo/content {:todo/child '...}])
:ident :todo/id}
(div (p {:style {:background-color "red"}} content)
(ui-editor {:content content :id id :this this})
(when (seq child)
(dom/ul
(dom/div
;; (js/console.log "this ->" content id)
;; (js/console.log child)
(map (fn [p] (ui-todo p)) child))))))
(def ui-todo (comp/factory Todo {:keyfn :todo/id}))
(defsc Root [this {all-todo :all-todo :as props}]
{:query [{:all-todo (comp/get-query Todo)}]}
(div
(div "todos: "
(map ui-todo all-todo))))
The mock data used:
(def todo-data
[{:todo/id 1
:todo/content "task"
:todo/child [[:todo/id 3] [:todo/id 4]]}
{:todo/id 2
:todo/content "task 2"
:todo/child []}
{:todo/id 3
:todo/content "task 3"
:todo/child [[:todo/id 5]]}
{:todo/id 4
:todo/content "task 4"
:todo/child []}
{:todo/id 5
:todo/content "task 5"
:todo/child []}])
(merge/merge-component! app ui/Todo todo-data
:replace [:all-todo])
when you merge data you should NOT use normalized data. You should have {:todo/id 3} in place of [:todo/id 3] in your child ref. for example. If todo 3 isn’t staying in sync it is because you don’t have it really normalized, which this initialization might be the cause of.
Yes, in a plain react component like you’ve build, you can send this through props. NOTE: There is a hooks ns in Fulcro that has wrappers for the hook functions.
@U0CKQ19AQ Hi, Tony. Thanks for your help! I tried your method and it doesn't seem to work. Like the video below, the second "task 5 textarea" is not synced with the edited text.
This is a slate.js thing, not fulcro.
The value
prop of the Slate
component only sets the initial state.
(def ui-slate (interop/react-factory Slate))
(def ui-editable (interop/react-factory Editable))
(defmutation change-context [{id :id data :new-data}]
(action [{:keys [state]}]
(swap! state assoc-in [:todo/id id :todo/content] data)))
(defsc Editor [_ {:keys [content]} {:keys [onChange]}]
{:use-hooks? true}
(let [[editor] (hooks/use-state #(-> (createEditor) (withReact)))
init-val [{"type" "paragraph"
"children" [{"text" content}]}]]
(set! (.-children editor) (clj->js init-val))
(ui-slate
{:editor editor
:value init-val
:onChange onChange}
(ui-editable {}))))
(def ui-editor (comp/computed-factory Editor))
(defsc Todo [this {:todo/keys [id content child]}]
{:query [:todo/id :todo/content {:todo/child '...}]
:ident :todo/id
:initial-state
(fn [{:todo/keys [id content child]}]
#:todo{:id id
:content content
:child (mapv #(comp/get-initial-state Todo %) child)})}
(div (p {:style {:background-color "red"}} content)
(ui-editor {:content content :id id}
{:onChange (fn [data]
(let [children (.-children (first data))
all-data (apply str (map #(.-text %) children))]
(comp/transact! this [(change-context {:id id :new-data all-data})])))})
(when (seq child)
(dom/ul
(dom/div
;; (js/console.log "this ->" content id)
;; (js/console.log child)
(map (fn [p] (ui-todo p)) child))))))
(def ui-todo (comp/factory Todo {:keyfn :todo/id}))
You could call (set! (.-children editor) (clj->js init-val))
in the render function of your editor.
@U4VT24ZM3 Thanks for the help! I have to test this tomorrow. I also want to ask if I use defsc for the editor to make a fulcro component. Is it still possible to call Editor.end(editor, [])
like https://stackoverflow.com/a/69605303/3737707 ?
My concern is that the editor
in the code above is a react component instance instead of a fulcro component instance. So do I need to take extra steps to make this work?