reagent-project / reagent

A minimalistic ClojureScript interface to React.js
http://reagent-project.github.io/
MIT License
4.76k stars 414 forks source link

RFC: Provide a React hook for using ratoms/reactions as part of public API #545

Open lilactown opened 3 years ago

lilactown commented 3 years ago

I've been working on a project for some time that integrates our existing reagent/re-frame application with some plain react components developed using helix. In order to do this, I had to do a lot of work to write an integration between reagent's ratoms/reactions and React hooks. I essentially have a custom hook called use-reaction that looks like:

(def db (r/atom {}))

;; somewhere in a React component
(let [db (use-reaction db) ;; subscribe to db and re-render on any changes
        ,,,]
   ,,,)
;; create a reaction that updates anytime `(:counter @db)` changes and re-render anytime it changes
(let [counter (use-reaction
               (react/useMemo
                #(r/reaction (:counter @db))
                #js []))]
   ,,,)

Currently this relies on a lot of internal Reagent machinery in order to properly set up watching and disposing the reaction. I think it would be appropriate to add such an integration point to Reagent proper, so that it can change along with any internal changes.

Would the maintainers be interested in accepting a PR for this?

Deraen commented 3 years ago

Yes, I'd be interested in including this.

I wonder if there would be some use for the other direction also? Creating a ratom backed by a hook?

lilactown commented 3 years ago

Thanks. It looks like there's some new development happening in React 18 which I think I would wait for before creating a hook for public consumption: https://github.com/reactwg/react-18/discussions/86

The above discussion lays out a plan for React to provide an API to make synchronizing external state (i.e. ratoms) easy to do in concurrent mode.

I wonder if there would be some use for the other direction also? Creating a ratom backed by a hook?

I can't imagine a case right now, and since React component state has different scheduling behavior than ratoms (i.e. they update async, not sync) I would also imagine it would be confusing for users.

kovasap commented 2 years ago

I'm in the process of using the react-data-grid DataGrid react component and am trying to get column sorting working in CLJS by following this JS example: https://github.com/adazzle/react-data-grid/blob/b7ad586498ab8a6ed3235ccfd93d3d490b24f4cc/website/demos/CommonFeatures.tsx#L330

My code so far is below, and IIUC, your use-reaction hook looks like it's the key piece I need to make the column sorting logic work. The problem currently for me is that I can update my sorted-rows atom, but the component itself does not update with the new sorted-rows value.

Am I understanding what's you've made correctly? If so, where can I find the code so that I can use it for my case?

(defn maps-to-datagrid
  [maps]
  (let [sorted-rows (r/atom maps)]
    [:div
      [:> DataGrid
       {:columns (clj->js (map datagrid-column (keys (first maps))))
        :defaultColumnOptions #js {:sortable true
                                   :resizable true}
        :sortColumns #js [#js {:columnKey "input" :direction "ASC"}]
        :onSortColumnsChange
        (fn [newSortColumns]
          (let [{columnKey :columnKey
                 direction :direction} (first (js->clj newSortColumns
                                                       :keywordize-keys true))]
            (swap! sorted-rows
                   #(sort (fn [m1 m2]
                            (prn m1 m2)
                            (let [v1 (get columnKey m1)
                                  v2 (get columnKey m2)]
                              (if (= direction "ASC")
                                (< v1 v2)
                                (> v1 v2))))
                          %))))
        :rows (clj->js @sorted-rows)}]
      [:button {:on-click #(csv/download-as-csv maps "data.csv")}
       "Download as CSV"]]))
lilactown commented 2 years ago

@kovasap the problem with your code seems unrelated to this issue. I'd suggest opening a separate one, or asking in the #reagent channel in clojurians.net slack team for help.

Deraen commented 2 years ago

useSyncExternalStore is now available with React 18.