clojurescriptre-frameclojurescript-javascript-interop

Animate antizer table with rc-animate in re-frame app


I am trying to recreate the example in http://react-component.github.io/table/examples/animation.html to add animation to a table in a re-frame app. The table is rendered using antizer which is a ClojureScript library for Ant Design react components. For the animation I'm trying to use rc-animate (as in the example) which is a JavaScript library. To integrate rc-animate, I followed the official Webpack guide and created a src/js/index.js file:

import Animate from 'rc-animate';
window.Animate = Animate;

My project.clj is:

(defproject ant-table-animation "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.10.238"]
                 [reagent "0.8.1"]
                 [re-frame "0.10.5" :exclusions [reagent]]
                 [antizer "0.3.1"]]

  :plugins [[lein-cljsbuild "1.1.7"]]

  :min-lein-version "2.5.3"

  :source-paths ["src/clj" "src/cljs"]

  :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]

  :figwheel {:css-dirs ["resources/public/css"]}

  :profiles
  {:dev
   {:dependencies [[binaryage/devtools "0.9.10"]
                   [cider/piggieback "0.3.9"]
                   [figwheel-sidecar "0.5.16"]
                   [day8.re-frame/re-frame-10x "0.3.3"]]

    :plugins      [[lein-figwheel "0.5.16"]]}
   :prod { }
   }

  :cljsbuild
  {:builds
   [{:id           "dev"
     :source-paths ["src/cljs"]
     :figwheel     {:on-jsload "ant-table-animation.core/mount-root"}
     :compiler     {:closure-defines {re-frame.trace.trace_enabled_QMARK_ true}
                    :main                 ant-table-animation.core
                    :output-to            "resources/public/js/compiled/app.js"
                    :output-dir           "resources/public/js/compiled/out"
                    :asset-path           "js/compiled/out"
                    :source-map-timestamp true
                    :preloads             [devtools.preload, day8.re-frame-10x.preload]
                    :external-config      {:devtools/config {:features-to-install :all}}
                    :infer-externs true
                    :npm-deps false
                    :foreign-libs [{:file "dist/index_bundle.js"
                                    :provides ["rc-animate" "rc-animate-child"]
                                    :global-exports {rc-animate Animate
                                                 rc-animate-child AnimateChild}}]
                    }}

    {:id           "min"
     :source-paths ["src/cljs"]
     :compiler     {:main            ant-table-animation.core
                    :output-to       "resources/public/js/compiled/app.js"
                    :optimizations   :advanced
                    :closure-defines {goog.DEBUG false}
                    :pretty-print    false}}


    ]}
  )

and in my views.cljs I try to render the table like this:

(ns ant-table-animation.views
  (:require
   [re-frame.core :as re-frame]
   [ant-table-animation.subs :as subs]
   [ant-table-animation.events :as events]
   [antizer.reagent :as ant]
   [reagent.core :as reagent]
   [rc-animate]
   ))

(.log js/console rc-animate)

(defn AnimateBody
  [props]
  (.createElement
    js/React
    rc-animate
    (.assign js/Object #js {:transitionName "move", :component "tbody"} props)))

(.log js/console AnimateBody)

(defn orders
  []
  (let [orders @(re-frame/subscribe [::subs/orders])
        width 80]
    [ant/table
     {:columns
      [{:title "Product Name" :dataIndex :product :width width}
       {:title "Quantity" :dataIndex :quantity :width width}
       {:title "Unit Price" :dataIndex :price :width width}
       {:title "Actions" :dataIndex "actions" :width width
        :render
        #(reagent/as-element
          [ant/button
           {:icon "delete" :type "danger"
            :on-click
            (fn []
              (re-frame/dispatch [::events/order-deleted (.-product %2)]))}])}]
      :dataSource orders
      :size "small"
      :components {:body {:wrapper AnimateBody}}
      :pagination {:page-size 20}
      :scroll {:y 300}}]))

(defn main-panel []
  (let [name (re-frame/subscribe [::subs/name])]
    [:div
     [:h1 "Hello from " @name]
     [orders]
     ]))

I am not sure at all about the

(defn AnimateBody
  [props]
  (.createElement
    js/React
    rc-animate
    (.assign js/Object #js {:transitionName "move", :component "tbody"} props)))

being equivalent to the line from the example

const AnimateBody = props => <Animate transitionName="move" component="tbody" {...props} />;

The table renders correctly, but when I try to delete a row it fails with the following error trace:

react-dom.development.js:55 Uncaught Error: Unable to find node on an unmounted component.
at invariant (react-dom.development.js:55)
at findCurrentFiberUsingSlowPath (react-dom.development.js:4256)
at findCurrentHostFiber (react-dom.development.js:4266)
at findHostInstance (react-dom.development.js:17676)
at Object.findDOMNode (react-dom.development.js:18145)
at AnimateChild.transition (AnimateChild.js:83)
at AnimateChild.componentWillLeave (AnimateChild.js:70)
at performLeave (Animate.js:339)
at Array.forEach (<anonymous>)
at Animate.componentDidUpdate (Animate.js:188)
at commitLifeCycles (react-dom.inc.js:15386)
at commitAllLifeCycles (react-dom.inc.js:16646)
at HTMLUnknownElement.callCallback (react-dom.inc.js:143)
at Object.invokeGuardedCallbackDev (react-dom.inc.js:193)
at invokeGuardedCallback (react-dom.inc.js:250)
at commitRoot (react-dom.inc.js:16800)
at completeRoot (react-dom.inc.js:18192)
at performWorkOnRoot (react-dom.inc.js:18120)
at performWork (react-dom.inc.js:18024)
at performSyncWork (react-dom.inc.js:17996)
at requestWork (react-dom.inc.js:17884)
at scheduleWork (react-dom.inc.js:17689)
at Object.enqueueForceUpdate (react-dom.inc.js:11855)
at Object.Component.forceUpdate (react.inc.js:479)
at reagent$impl$batching$run_queue (batching.cljs?rel=1541330682770:39)
at Object.flush_queues (batching.cljs?rel=1541330682770:86)
at Object.run_queues (batching.cljs?rel=1541330682770:76)
at batching.cljs?rel=1541330682770:63
at re_frame_10x.cljs?rel=1541164419576:125

It is also indicated that:

The above error occurred in the <Animate> component:
in Animate (created by ant_table_animation.views.animateBody)
in ant_table_animation.views.animateBody (created by BaseTable)
in table (created by BaseTable)
in BaseTable (created by Connect(BaseTable))
in Connect(BaseTable) (created by BodyTable)
in div (created by BodyTable)
in BodyTable (created by ExpandableTable)
in div (created by ExpandableTable)
in div (created by ExpandableTable)
in div (created by ExpandableTable)
in ExpandableTable (created by Connect(ExpandableTable))
in Connect(ExpandableTable) (created by Table)
in Provider (created by Table)
in Table (created by LocaleReceiver)
in LocaleReceiver (created by Table)
in div (created by Spin)
in AnimateChild (created by Animate)
in div (created by Animate)
in Animate (created by Spin)
in Spin (created by Table)
in div (created by Table)
in Table (created by ant_table_animation.views.orders)
in ant_table_animation.views.orders (created by    ant_table_animation.views.main_panel)
in div (created by ant_table_animation.views.main_panel)
in ant_table_animation.views.main_panel

I am a Clojure beginner, and I know even less for React; this is where I ended up after a week of trying but now I feel stuck. I have uploaded my project in github for anyone that would like to give it a try.


Solution

  • The Unable to find node on an unmounted component error was occurring because of version issues with React. I was able to handle it by explicitly using the version of React used by the rc-animate library - 16.5.2 in my project.clj:

    ...             
    [reagent "0.8.1" :exclusions [cljsjs/react cljsjs/react-dom [cljsjs/react-dom-server]]]
    [cljsjs/react "16.5.2-0"]
    [cljsjs/react-dom "16.5.2-0"]
    [cljsjs/react-dom-server "16.5.2-0"]
        ...
    [antizer "0.3.1" :exclusions [cljsjs/react cljsjs/react-dom [cljsjs/react-dom-server]]]]
        ...
    

    For the correct definition of the AnimateBody component I had to use a combination of reagent/adapt-react-class, reagent/as-element and reagent/reactify-component. Specifically, in my views.cljs I define the component as:

    (def animate (reagent/adapt-react-class rc-animate))
    (def animateBody
      (fn [props]
        (reagent/as-element [animate (assoc props :transition-name "move" :component "tbody")])))
    

    and then pass it to the ant/table component with:

    ...
    :components {:body {:wrapper (reagent/reactify-component animateBody)}}
    ...